一、继承在JVM里面是什么样子的
二、super关键字
1. 访问父类的成员变量
当子类中定义的成员变量与父类的成员变量同名时,为了在子类中访问父类的成员变量,可以使用 super
关键字。
class Parent {
int value = 10;
}
class Child extends Parent {
int value = 20;
public void printValues() {
// 访问子类的成员变量
System.out.println("子类的 value: " + value);
// 访问父类的成员变量
System.out.println("父类的 value: " + super.value);
}
}
public class Main {
public static void main(String[] args) {
Child child = new Child();
child.printValues();
}
}
在上述代码中,Child
类继承自 Parent
类,并且都定义了名为 value
的成员变量。在 Child
类的 printValues
方法中,使用 value
访问的是子类的成员变量,而使用 super.value
访问的是父类的成员变量。
2. 调用父类的方法
当子类重写了父类的方法后,如果需要在子类的方法中调用父类被重写的方法,可以使用 super
关键字。
class Parent {
public void display() {
System.out.println("这是父类的 display 方法");
}
}
class Child extends Parent {
@Override
public void display() {
// 调用父类的 display 方法
super.display();
System.out.println("这是子类的 display 方法");
}
}
public class Main {
public static void main(String[] args) {
Child child = new Child();
child.display();
}
}
在上述代码中,Child
类重写了 Parent
类的 display
方法。在 Child
类的 display
方法中,使用 super.display()
调用了父类的 display
方法,然后再执行子类自身的逻辑。
3. 调用父类的构造方法
在子类的构造方法中,可以使用 super
关键字调用父类的构造方法。需要注意的是,super
调用必须是子类构造方法中的第一条语句。
class Parent {
private int value;
public Parent(int value) {
this.value = value;
System.out.println("父类的构造方法被调用,value = " + value);
}
}
class Child extends Parent {
public Child(int value) {
// 调用父类的构造方法
super(value);
System.out.println("子类的构造方法被调用");
}
}
public class Main {
public static void main(String[] args) {
Child child = new Child(10);
}
}
在上述代码中,Child
类的构造方法中使用 super(value)
调用了父类的构造方法,先执行父类的构造逻辑,然后再执行子类的构造逻辑。
总结
super
关键字主要用于在子类中访问父类的成员变量、调用父类的方法和构造方法。- 在访问父类成员变量和方法时,
super
可以帮助我们区分子类和父类中同名的成员。 - 在调用父类构造方法时,
super
调用必须放在子类构造方法的第一行,以确保父类对象先被正确初始化。
三、super和this调用构造方法的区别
this
关键字:用于在一个构造方法中调用同一个类的其他构造方法,this()
调用必须放在构造方法的第一行。
class Student {
private String name;
private int age;
public Student() {
// 调用另一个构造方法
this("未知", 0);
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
super
关键字:用于在子类的构造方法中调用父类的构造方法,super()
调用同样必须放在子类构造方法的第一行,以确保父类对象先被正确初始化。
class Parent {
public Parent() {
System.out.println("父类的无参构造方法被调用");
}
}
class Child extends Parent {
public Child() {
// 调用父类的无参构造方法
super();
System.out.println("子类的无参构造方法被调用");
}
}
四、super和this只能在非静态方法中使用
1. this
关键字只能在非静态方法中使用的原因
this
的本质:this
关键字代表当前对象的引用,它指向正在调用该方法的对象实例。也就是说,this
依赖于对象的存在,只有当对象被创建后,this
才有实际的指向意义。- 静态方法的特性:静态方法属于类本身,不依赖于任何对象实例。在类加载时,静态方法就已经存在于内存中,此时还没有创建任何对象实例。如果在静态方法中使用
this
关键字,由于没有对象实例,this
就没有具体的指向,会导致逻辑错误。
public class ThisInStaticExample {
private int value;
// 静态方法
public static void staticMethod() {
// 编译错误,不能在静态方法中使用 this
// System.out.println(this.value);
}
// 非静态方法
public void nonStaticMethod() {
// 可以在非静态方法中使用 this
System.out.println(this.value);
}
}
2. super
关键字只能在非静态方法中使用的原因
super
的本质:super
关键字代表当前对象的父类对象的引用,它同样依赖于对象实例。只有当子类对象被创建后,通过该对象才能访问其父类的成员,super
才有实际的指向和作用。- 静态方法的特性:静态方法不与任何对象实例关联,它是类级别的方法。在静态方法执行时,没有对象实例存在,也就无法确定
super
所指向的父类对象,因此在静态方法中使用super
会导致编译错误。
class Parent {
protected int parentValue;
}
class Child extends Parent {
// 静态方法
public static void staticMethod() {
// 编译错误,不能在静态方法中使用 super
// System.out.println(super.parentValue);
}
// 非静态方法
public void nonStaticMethod() {
// 可以在非静态方法中使用 super
System.out.println(super.parentValue);
}
}
五、子类构造方法
在 Java 中,子类的构造方法默认会调用父类的无参构造方法。如果父类没有无参构造方法,子类的构造方法必须显式地调用父类的有参构造方法。
子类构造方法 super(value)在前,后初始化子类特有的属性,先帮父类部分初始化。
// 父类
class Parent {
private int value;
// 有参构造方法
public Parent(int value) {
this.value = value;
System.out.println("Parent 构造方法被调用,value: " + value);
}
}
// 子类
class Child extends Parent {
private String name;
// 子类构造方法
public Child(int value, String name) {
// 显式调用父类的有参构造方法
super(value);
this.name = name;
System.out.println("Child 构造方法被调用,name: " + name);
}
}
public class Main {
public static void main(String[] args) {
Child child = new Child(10, "Alice");
}
}
代码解释:
Parent
类有一个有参构造方法,用于初始化value
属性。Child
类继承自Parent
类,它的构造方法中使用super(value)
显式调用了父类的有参构造方法。- 在
main
方法中创建Child
对象时,会先调用父类的构造方法,再调用子类的构造方法。
字类调用父类的构造方法来初始化,顺序是:父类的静态块,子类的静态块,父类的实例块和构造方法,子类的实例块和构造方法。
六、final关键字
1. final
修饰类
当 final
修饰一个类时,意味着这个类不能被继承,即它不能有子类。常用于设计一些不希望被其他类扩展的类,比如 Java 标准库中的 String
类就是 final
类。
final class FinalClass {
public void display() {
System.out.println("This is a final class.");
}
}
// 以下代码会编译错误,因为 FinalClass 不能被继承
// class SubFinalClass extends FinalClass { }
public class Main {
public static void main(String[] args) {
FinalClass fc = new FinalClass();
fc.display();
}
}
2. final
修饰方法
如果 final
修饰一个方法,那么这个方法不能被其子类重写(覆盖)。这可以保证该方法的实现逻辑不被改变,常用于确保某些核心逻辑的稳定性。
class ParentClass {
public final void finalMethod() {
System.out.println("This is a final method.");
}
}
class ChildClass extends ParentClass {
// 以下代码会编译错误,因为 finalMethod 不能被重写
// public void finalMethod() { }
}
public class Main {
public static void main(String[] args) {
ParentClass pc = new ChildClass();
pc.finalMethod();
}
}
3. final
修饰变量
- 基本数据类型变量:一旦被赋值,其值就不能再被改变,相当于常量。
public class Main {
public static void main(String[] args) {
final int num = 10;
// 以下代码会编译错误,因为 num 是 final 变量,不能重新赋值
// num = 20;
System.out.println(num);
}
}
- 引用数据类型变量:变量所引用的对象不能再改变,但对象的内容可以改变。
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
final List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
// 以下代码会编译错误,因为 list 引用不能再改变
// list = new ArrayList<>();
System.out.println(list);
}
}
七、protect权限:不同包的子类可以访问
在 Java 中,protected
是一种访问权限限定符,用于控制类的成员(字段、方法等)的访问范围。protected
权限介于 private
和 public
之间,下面详细介绍其使用方法和特点。
1. 基本概念
protected
成员可以被以下几种情况访问:
- 同一个类内部可以访问。
- 同一个包内的其他类可以访问。
- 不同包的子类可以访问(通过继承)。
2. 示例代码
同一个包内的访问
// 同一个包内的父类
package com.example.demo;
public class Parent {
protected int protectedField = 10;
protected void protectedMethod() {
System.out.println("This is a protected method.");
}
}
// 同一个包内的另一个类
package com.example.demo;
public class SamePackageClass {
public static void main(String[] args) {
Parent parent = new Parent();
// 可以访问 protected 字段
System.out.println(parent.protectedField);
// 可以访问 protected 方法
parent.protectedMethod();
}
}
不同包的子类访问
// 父类,位于 com.example.parent 包
package com.example.parent;
public class Parent {
protected int protectedField = 10;
protected void protectedMethod() {
System.out.println("This is a protected method.");
}
}
// 子类,位于 com.example.child 包
package com.example.child;
import com.example.parent.Parent;
public class Child extends Parent {
public void accessParentProtectedMembers() {
// 可以访问从父类继承的 protected 字段
System.out.println(this.protectedField);
// 可以访问从父类继承的 protected 方法
this.protectedMethod();
}
public static void main(String[] args) {
Child child = new Child();
child.accessParentProtectedMembers();
}
}
不同包且非子类的访问限制
// 父类,位于 com.example.parent 包
package com.example.parent;
public class Parent {
protected int protectedField = 10;
protected void protectedMethod() {
System.out.println("This is a protected method.");
}
}
// 不同包且非子类的类
package com.example.other;
import com.example.parent.Parent;
public class OtherClass {
public static void main(String[] args) {
Parent parent = new Parent();
// 以下代码会编译错误,因为 OtherClass 不是 Parent 的子类,且不在同一个包内
// System.out.println(parent.protectedField);
// parent.protectedMethod();
}
}
3. 总结
protected
权限限定符提供了一种灵活的访问控制机制,它允许在同一个包内的类以及不同包的子类访问类的成员,同时限制了其他类的访问,有助于封装和信息隐藏,提高代码的安全性和可维护性。
八、类的修饰符为什么只能是public或者什么都不写
在 Java 里,顶层类(直接定义在包中的类)的修饰符通常只能是 public
或者默认(即什么都不写),这主要和 Java 的访问控制机制以及设计理念有关,下面详细阐述:
访问控制机制
Java 提供了四种访问修饰符,分别是 public
、protected
、private
和默认(没有显式修饰符),它们各自有不同的访问权限范围:
public
:被public
修饰的类、方法或字段可以在任何地方被访问。protected
:被protected
修饰的成员能在同一个包内的类中访问,也能在不同包的子类中访问。- 默认(无修饰符):默认访问权限也称为包访问权限,意味着该类、方法或字段只能在同一个包内的其他类中访问。
private
:被private
修饰的成员只能在定义它的类内部访问。
对于顶层类而言,使用 protected
和 private
修饰并不符合逻辑:
protected
:protected
主要用于控制子类对父类成员的访问权限,而顶层类不存在父类 - 子类这种层次关系,所以使用protected
修饰顶层类没有实际意义。private
:如果顶层类被private
修饰,那就意味着该类只能在定义它的类内部访问,这使得这个类无法被其他任何类使用,这样的类几乎没有实际用途,所以 Java 不允许使用private
修饰顶层类。
代码示例说明
-
public
修饰的顶层类
// 定义一个 public 修饰的顶层类
package com.example;
public class PublicClass {
public void publicMethod() {
System.out.println("This is a public method in a public class.");
}
}
这个 PublicClass
可以在任何包的类中被访问和使用,如下:
package another.example;
import com.example.PublicClass;
public class Main {
public static void main(String[] args) {
PublicClass pc = new PublicClass();
pc.publicMethod();
}
}
-
default 默认修饰的顶层类
// 定义一个默认修饰的顶层类
package com.example;
class DefaultClass {
public void defaultMethod() {
System.out.println("This is a method in a default class.");
}
}
这个 DefaultClass
只能在 com.example
包内的其他类中被访问,若在不同包的类中访问则会编译错误:
// 在同一个包内访问默认类
package com.example;
public class Test {
public static void main(String[] args) {
DefaultClass dc = new DefaultClass();
dc.defaultMethod();
}
}
总结
顶层类使用 public
或者默认修饰符,是为了确保类的访问控制符合逻辑和实际使用场景。public
允许类在全局范围内被使用,而默认修饰符则将类的使用范围限制在同一个包内,这样的设计保证了 Java 程序的安全性和可维护性。不过,内部类可以使用 protected
和 private
修饰,因为内部类存在于外部类的内部,有明确的层次关系,这些修饰符能更好地控制内部类的访问权限。
九、继承和组合
继承和组合是面向对象编程中两种重要的代码复用和组织方式,它们各有特点和适用场景,下面将详细介绍这两种方式。
继承(Inheritance)
概念
继承是指一个类(子类、派生类)可以继承另一个类(父类、基类)的属性和方法,子类可以复用父类的代码,并且可以在此基础上添加新的属性和方法,或者重写父类的方法以实现不同的行为。继承体现了 “is-a” 的关系,即子类是父类的一种特殊类型。
语法示例(以 Java 为例)
// 父类
class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println(name + " is eating.");
}
}
// 子类
class Dog extends Animal {
public Dog(String name) {
super(name);
}
public void bark() {
System.out.println(name + " is barking.");
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog("Buddy");
dog.eat();
dog.bark();
}
}
代码解释
Animal
是父类,定义了name
属性和eat
方法。Dog
是子类,继承自Animal
类,通过extends
关键字实现。Dog
类可以使用Animal
类的name
属性和eat
方法,同时还定义了自己的bark
方法。
优点
- 代码复用:子类可以直接使用父类的属性和方法,减少了代码的重复编写。
- 多态性:通过继承可以实现多态,提高代码的灵活性和可扩展性。
缺点
- 耦合度高:子类与父类之间的耦合度较高,父类的修改可能会影响到子类。
- 继承层次过深:如果继承层次过多,会导致代码的可读性和可维护性降低。
组合(Composition)
概念
组合是指一个类包含另一个类的对象作为其成员变量,通过调用这些对象的方法来实现代码复用。组合体现了 “has-a” 的关系,即一个类拥有另一个类的对象。
语法示例(以 Java 为例)
// 类 A
class Engine {
public void start() {
System.out.println("Engine is starting.");
}
}
// 类 B
class Car {
private Engine engine;
public Car() {
this.engine = new Engine();
}
public void startCar() {
engine.start();
System.out.println("Car is starting.");
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car();
car.startCar();
}
}
代码解释
Engine
类表示汽车的发动机,有一个start
方法。Car
类包含一个Engine
类的对象作为成员变量,通过调用engine.start()
方法来实现汽车启动的功能。
优点
- 低耦合:类之间的耦合度较低,一个类的修改不会影响到其他类。
- 灵活性高:可以在运行时动态地改变组合对象,提高代码的灵活性。
缺点
- 代码量增加:需要创建和管理多个对象,代码量相对较多。
继承和组合的选择
- 使用继承的场景:当两个类之间存在明显的 “is-a” 关系,并且需要使用多态性时,适合使用继承。例如,猫是动物的一种,狗也是动物的一种,可以使用继承来表示这种关系。
- 使用组合的场景:当两个类之间存在 “has-a” 关系,并且希望降低类之间的耦合度时,适合使用组合。例如,汽车拥有发动机,电脑拥有硬盘等,可以使用组合来表示这种关系。
- 在实际编程中,通常会根据具体的需求和设计目标来选择使用继承还是组合,有时候也会将两者结合使用,以达到更好的代码复用和组织效果。