目录
11、继承
在Java中,继承是一种面向对象编程的机制,用于创建新的类(称为子类或派生类)通过继承已有的类(称为父类或基类)。通过继承,子类可以继承父类的属性和方法,从而实现代码的重用和扩展。
继承的语法形式如下:
class ChildClass extends ParentClass {
// 子类的成员变量、方法等
}
在这个语法中,
ChildClass
是子类,ParentClass
是父类。子类通过关键字extends
来继承父类,表示子类从父类派生而来。继承带来了以下几个重要的概念:
子类拥有父类的属性和方法:子类继承了父类的所有非私有成员变量和方法。这意味着子类可以使用父类的属性和方法,无需重新编写相同的代码。
子类可以扩展父类的功能:子类可以在继承的基础上新增自己的成员变量和方法,从而实现对父类功能的扩展。
子类可以重写父类的方法:子类可以根据需要重写(覆盖)从父类继承的方法,以满足子类自身的行为需求。
子类可以通过super关键字调用父类的构造方法:在子类的构造方法中,可以使用
super
关键字来调用父类的构造方法,以初始化继承自父类的部分。需要注意以下几点:
Java中的继承是单继承的,即一个子类只能继承一个父类。但是,子类可以通过多层继承的方式继承更上层的父类。
子类可以访问父类的公有(public)和受保护(protected)成员变量和方法,但无法直接访问私有(private)成员变量和方法。
如果子类重写了父类的方法,可以使用
@Override
注解来标记,以提高代码的可读性和安全性。继承是Java中实现代码重用和构建面向对象系统的重要机制之一。通过继承,可以构建更高层次的类层次结构,从而提供更强大和灵活的代码功能。
12、super关键字
在Java中,
super
是一个关键字,用于表示当前子类对象所继承的父类对象。它可以用来访问父类的成员变量、方法和构造方法。使用
super
关键字有以下几种常见的用法:
访问父类的成员变量和方法:子类可以使用
super
关键字来访问父类继承的成员变量和方法。一般形式是super.成员变量
和super.方法名()
。这样可以区分子类中与父类同名的成员变量和方法。在子类构造方法中调用父类构造方法:在子类的构造方法中使用
super
关键字来调用父类的构造方法。这样可以先初始化继承自父类的部分,然后再完成子类特有的初始化操作。调用父类构造方法的语法形式为super()
。在子类方法中调用父类被重写的方法:当子类重写了父类的方法,但又希望在子类方法中调用父类被重写的方法时,可以使用
super
关键字。这样可以实现在子类方法中扩展父类方法的功能。需要注意的是,
super
关键字只能在子类中使用,用于引用父类对象,它不能被用于static
方法中,因为static
方法不依赖于具体的对象。下面是一个示例代码,演示了
super
关键字的几种用法:
class ParentClass {
String name = "Parent";
void display() {
System.out.println("Parent display");
}
}
class ChildClass extends ParentClass {
String name = "Child";
void display() {
super.display(); // 调用父类的display方法
System.out.println("Child display");
}
void printName() {
System.out.println(super.name); // 访问父类的name成员变量
}
ChildClass() {
super(); // 调用父类的构造方法
}
}
public class Main {
public static void main(String[] args) {
ChildClass obj = new ChildClass();
obj.display(); // 调用子类的display方法
obj.printName(); // 调用子类的printName方法
}
}
执行上述代码将会输出以下结果:
Parent display
Child display
Parent
可以看到,通过super
关键字,子类方法中既能调用父类的方法,又能访问父类的成员变量。这样可以灵活地操作继承关系,实现代码的扩展和复用。
13、方法的重写
在Java中,方法重写(Method Overriding)是指子类中定义的方法与父类中具有相同名称、参数列表和返回类型的方法进行覆盖的过程。子类通过重写父类的方法,可以改变方法的实现细节,以实现子类特有的功能。
要在子类中重写父类的方法,需要满足以下条件:
方法名称、参数列表和返回类型与父类方法相同:子类中的方法必须与父类中被重写的方法具有相同的名称、参数列表和返回类型。方法名称和参数列表合称为方法签名。
访问级别不能更严格:子类中重写的方法的访问级别不能是比父类中被重写的方法更严格。比如,如果父类中的方法是public访问级别,子类中重写的方法可以是public、protected或默认(包内可见)访问级别,但不能是私有(private)。
子类方法不能抛出比父类方法更宽泛的异常:如果父类方法声明了异常,子类方法重写时抛出的异常类型不能是父类方法抛出异常类型的子类。只能是相同的异常类型或不抛出异常。
子类方法不能使用比父类方法更严格的修饰符:重写的方法不能使用比父类方法更严格的访问修饰符。比如,如果父类方法是public,子类方法不能是private。
示例代码如下所示:
class ParentClass {
public void sayHello() {
System.out.println("Hello from ParentClass");
}
}
class ChildClass extends ParentClass {
@Override
public void sayHello() {
System.out.println("Hello from ChildClass");
}
}
public class Main {
public static void main(String[] args) {
ParentClass parent = new ParentClass();
parent.sayHello(); // 输出: Hello from ParentClass
ChildClass child = new ChildClass();
child.sayHello(); // 输出: Hello from ChildClass
ParentClass childPolymorphism = new ChildClass();
childPolymorphism.sayHello(); // 输出: Hello from ChildClass
}
}
在上述示例中,
ChildClass
继承了ParentClass
,并重写了sayHello
方法。通过创建对象并调用方法,可以看到在不同情况下,所调用的方法根据对象的类型确定。需要注意的是,方法重写只发生在子类与父类之间的关系中,同一个类中的方法不能被重写,但可以被重载(Method Overloading)。同时,方法重写只能改变方法的实现,不能改变方法的名称、参数列表和返回类型。这样可以确保代码的一致性和可读性。
14、多态
在Java中,多态(Polymorphism)是指同一个类型的对象,在不同的情况下表现出不同的行为。具体而言,它包括两种形式:静态多态和动态多态。
- 静态多态(Static Polymorphism)也被称为编译时多态(Compile-time Polymorphism)或方法重载(Method Overloading)。它是在编译时根据方法的名称、参数类型和参数数量来决定调用哪个方法。编译器根据方法的签名匹配进行函数重载的解析。
示例代码如下所示:
class Calculator {
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
}
public class Main {
public static void main(String[] args) {
Calculator calculator = new Calculator();
int result1 = calculator.add(3, 4); // 静态多态,调用int版本的add方法
System.out.println(result1); // 输出: 7
double result2 = calculator.add(2.5, 3.7); // 静态多态,调用double版本的add方法
System.out.println(result2); // 输出: 6.2
}
}
在上述示例中,
Calculator
类中定义了两个名为add
的方法,一个接受int
类型的参数,另一个接受double
类型的参数。在main
方法中,根据传入的参数类型,编译器选择了适当的方法进行调用2.动态多态(Dynamic Polymorphism)也被称为运行时多态(Runtime Polymorphism)或方法重写(Method Overriding)。它是指通过继承和重写父类方法,在运行时根据对象的实际类型来决定调用哪个方法。这种行为是通过方法重写和使用父类引用指向子类对象来实现的。
示例代码如下所示:
class Animal {
public void makeSound() {
System.out.println("Animal makes sound");
}
}
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Cat meows");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Animal animal1 = new Cat();
Animal animal2 = new Dog();
animal1.makeSound(); // 动态多态,调用Cat类中重写的makeSound方法,输出: Cat meows
animal2.makeSound(); // 动态多态,调用Dog类中重写的makeSound方法,输出: Dog barks
}
}
在上述示例中,
Animal
类是父类,Cat
和Dog
类是子类,它们都重写了父类的makeSound
方法。在main
方法中,使用父类引用指向子类对象,并调用makeSound
方法时,根据对象的实际类型,调用了相应子类中重写的方法。动态多态性允许我们以一种抽象的方式编写代码,使得代码更灵活、可扩展和可维护。它使得我们能够编写通用的代码,而不需要针对每个具体子类编写特定的代码逻辑。
15、final
在Java中,关键字
final
用于表示不可变性或者最终性。它可以用于不同的上下文,包括变量、方法和类。
final
变量:当我们声明一个变量为final
时,它表示该变量的值在初始化之后不能被修改。一旦给final
变量赋予了初始值,它就无法再被改变。final
变量通常与关键字static
一起使用作为常量(constant)来声明,常量的命名通常使用大写字母。示例代码如下:
final int x = 10;
x = 20; // 编译错误,无法修改final变量的值
final double PI = 3.14159;
final
方法:当我们将一个方法声明为final
时,子类无法对该方法进行重写(override)。这样做可以确保该方法的实现不会被子类修改。示例代码如下:
class Parent {
public final void display() {
System.out.println("This is a final method.");
}
}
class Child extends Parent {
public void display() { // 编译错误,无法重写final方法
System.out.println("This is overridden method.");
}
}
final
类:当我们将一个类声明为final
时,它表示该类不能被继承。也就是说,无法创建该类的子类。示例代码如下:
final class Parent {
// 类的定义
}
class Child extends Parent { // 编译错误,无法继承final类
// 类的定义
}
使用
final
关键字可以提供一些编译时和运行时的约束,从而增强程序的可靠性、安全性和可维护性。注意,final
关键字的使用应谨慎,不宜滥用。只有在确实需要某个实体不可变或不可扩展时,才应考虑使用final
。
16、抽象
在Java中,抽象(abstract)是一种特殊的关键字,用于定义抽象类和抽象方法。抽象类和抽象方法提供了一种机制,可以将共享的属性和行为定义在抽象类中,并且可以通过继承来实现这些抽象类的具体行为。
抽象类(Abstract Class):
抽象类是不能直接实例化的类,它通常用作其他类的父类。抽象类通过使用关键字abstract
进行声明,并且可以包含抽象方法和非抽象方法。抽象类中的抽象方法没有具体的实现,而是用关键字abstract
进行声明,并以分号结尾。示例代码如下:
abstract class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public abstract void sound(); // 抽象方法
public void sleep() {
System.out.println("Animal is sleeping.");
}
}
class Cat extends Animal {
public Cat(String name) {
super(name);
}
public void sound() {
System.out.println("Meow!"); // 实现抽象方法
}
}
抽象方法(Abstract Method):
抽象方法是没有具体实现的方法,只有方法的签名,以及关键字abstract
进行声明,并以分号结尾。抽象方法必须位于抽象类中,用于约束子类必须实现该方法。示例代码如下:
abstract class Animal {
public abstract void sound(); // 抽象方法
}
子类继承抽象类时,必须实现父类中的所有抽象方法。如果子类没有实现所有的抽象方法,那么子类也必须声明为抽象类。
抽象类和抽象方法的主要特点是:
- 抽象类不能用来创建对象,只能用作父类。
- 抽象方法没有具体的实现,子类必须实现抽象方法。
- 子类继承抽象类时,可以继承非抽象方法。
- 抽象类可以包含非抽象方法,这些方法可以直接在抽象类实例中调用。
抽象类和抽象方法给予了Java面向对象编程的灵活性和扩展性,使得我们能够通过继承来实现多态性和代码重用。
17、接口
在Java中,接口(Interface)是一种重要的概念,用于定义类应该实现的方法集合。接口定义了一组抽象方法和常量字段,可以通过实现该接口的类来提供具体的实现。
接口在Java中起到了定义契约和规范的作用,它定义了一组方法的签名,但没有具体的实现。实现接口的类必须提供对这些方法的具体实现。
下面是一个简单的接口示例:
public interface Shape {
double getArea(); // 抽象方法,计算形状的面积
double getPerimeter(); // 抽象方法,计算形状的周长
}
接口中的方法都是抽象的,不需要添加关键字
abstract
。接口中的字段默认为常量字段(public static final
),不需要显式使用这些关键字进行声明。这些字段在接口中是不可修改的。接口的特点如下:
- 接口中的方法都是抽象的,没有具体实现。
- 接口可以继承其他接口。一个类可以实现多个接口,实现多个接口的类必须提供所有接口中方法的具体实现。
- 接口可以在类的继承体系中起到多态的作用。可以使用接口引用指向实现了该接口的类的实例。
- 接口可以用于定义回调机制,通过接口可以将一个类的实例传递给另一个类,使得后者可以调用前者定义的方法。
下面是一个使用接口的示例:
public class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
public double getArea() {
return Math.PI * radius * radius;
}
public double getPerimeter() {
return 2 * Math.PI * radius;
}
}
public class Rectangle implements Shape {
private double width;
private double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
public double getArea() {
return width * height;
}
public double getPerimeter() {
return 2 * (width + height);
}
}
在上面的示例中,
Circle
类和Rectangle
类都实现了Shape
接口,提供了getArea()
和getPerimeter()
方法的具体实现。通过接口,我们可以实现多态性和代码重用,简化了系统的设计和扩展。接口在Java编程中被广泛应用,常用于定义API、设计模式和框架的扩展机制等场景。
18、成员变量初始值
在Java中,成员变量会有默认的初始值,具体取决于变量的类型。当对象被创建时,如果没有显式地给成员变量赋初始值,那么它们会被自动赋予默认值,如下所示:
- 对于数值类型(byte、short、int、long、float、double),默认值为0。
- 对于布尔类型(boolean),默认值为false。
- 对于字符类型(char),默认值为’\u0000’。
- 对于引用类型(类、接口、数组等),默认值为null。
以下是一些示例来说明默认初始值的概念:
public class MyClass {
private int myInt;
private boolean myBool;
private char myChar;
private String myString;
public static void main(String[] args) {
MyClass obj = new MyClass();
System.out.println(obj.myInt); // 输出:0
System.out.println(obj.myBool); // 输出:false
System.out.println(obj.myChar); // 输出:空字符(' ')
System.out.println(obj.myString); // 输出:null
}
}
在上述例子中,
MyClass
类的成员变量没有显式地赋初始值。当创建MyClass
对象并访问这些成员变量时,会发现它们拥有默认的初始值。需要注意的是,局部变量(方法中声明的变量)没有默认初始值,必须在使用之前进行显式初始化。如果尝试使用未初始化的局部变量,编译器会报错。
除了默认初始值,我们也可以通过构造函数或者初始化块为成员变量提供自定义的初始值。通过这种方式,我们可以在对象创建时,根据需求为成员变量赋予特定的初值。
19、object
在Java中,
Object
是所有类的根类。所有的类都直接或间接地继承自Object
类,因此Object
类在Java中具有特殊的地位。
Object
类定义了一些基本的方法,这些方法可供所有的Java对象使用。下面是一些常用的Object
类的方法:
equals(Object obj)
: 用于比较两个对象是否相等。默认情况下,它比较对象的引用地址,可以根据需要进行重写。
hashCode()
: 返回对象的哈希码值。哈希码用于在哈希表等数据结构中快速查找对象。
toString()
: 返回对象的字符串表示。默认情况下,它返回对象的类名和哈希码,可以根据需要进行重写。
getClass()
: 返回对象的运行时类。可以用于获取对象的类信息。
finalize()
: 由垃圾回收器调用的方法,在对象被垃圾回收之前进行一些清理工作。
Object
类还包含其他方法,如wait()
、notify()
、notifyAll()
等,用于实现线程间的协作和同步。所有的类,即使没有显式地声明继承
Object
类,也会自动继承它的方法。因此,可以在任何对象上调用Object
类的方法。除了上述方法,
Object
类还提供了一些辅助方法,如clone()
、finalize()
等,但这些方法需要进行重写才能实现自定义的行为。总之,
Object
类是Java类体系中的根类,提供了一些通用的方法供所有对象使用。通过继承Object
类,其他类可以从中获得这些方法的默认实现或进行重写,以满足特定的需求。
20、equals和==
在Java中,
equals()
方法和==
操作符都用于比较对象之间的相等性,但它们的行为有所不同。
==
操作符:
- 对于基本类型,
==
比较的是它们的值是否相等。如果两个基本类型的值相等,则返回true
;否则返回false
。- 对于引用类型,
==
比较的是两个对象的引用是否相等,即是否引用同一个对象。当两个引用指向同一个对象时,返回true
;否则返回false
。例如:
int a = 5;
int b = 5;
System.out.println(a == b); // 输出:true
String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1 == str2); // 输出:false
String str3 = str1;
System.out.println(str1 == str3); // 输出:true
在上述示例中,
a
和b
是基本类型变量,它们的值相等,因此a == b
返回true
。而str1
和str2
是引用类型变量,尽管它们的内容相同,但它们指向不同的对象,因此str1 == str2
返回false
。而str1
和str3
指向同一个对象,因此str1 == str3
返回true
。
equals()
方法:
equals()
方法用于比较对象的内容是否相等。默认情况下,equals()
方法和==
操作符的行为相同,即比较对象的引用是否相等。- 但是,可以通过在类中重写
equals()
方法来改变它的行为,以实现自定义的相等性比较逻辑。通常情况下,我们会重写equals()
方法来比较对象的内容,而不是引用。例如:
String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1.equals(str2)); // 输出:true
String str3 = null;
System.out.println(str1.equals(str3)); // 输出:false
在上述示例中,通过调用
equals()
方法比较字符串对象的内容,而不是引用。因为字符串的equals()
方法已经被重写,所以返回true
。需要注意的是,在使用
equals()
方法比较对象之前,应该先进行空值检查,以避免空指针异常。总结:
==
操作符用于比较基本类型的值和引用类型的引用地址。equals()
方法用于比较对象的内容是否相等,默认情况下与==
操作符的行为相同,比较引用地址。- 可以重写
equals()
方法来改变对象之间相等性比较的逻辑。