目录
1.继承
继承是面向对象编程中的一个概念,它允许一个类从另一个类继承属性和方法。被继承的类称为父类或基类,继承的类称为子类或派生类。子类可以继承父类的公共成员(如属性和方法),并且可以根据需要添加新成员或重写父类的成员。
继承可以帮助实现代码的重用和模块化,减少重复编写相似的代码的工作量。子类可以获得父类的属性和方法,并可以根据需要进行修改或扩展。通过继承,我们可以建立类的层次结构,从而更好地组织和管理代码。
//Dog.java(子类)
package demo1;
public class Dog extends Animals{
public void bark(){
System.out.println(this.name+" wang wang wang~~~");
}
}
//Cat.java(子类)
package demo1;
public class Cat extends Animals{
public void mew(){
System.out.println(this.name+" mi mi mi~~~");
}
}
//Animals.java(父类)
package demo1;
public class Animals {
public String name;
public int age;
public void eat(){
System.out.println(this.name+" is eating...");
}
}
//Test.java
package demo1;
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.name = "大黄";
dog.age = 2;
dog.eat();
dog.bark();
System.out.println("--------------------------");
Cat cat = new Cat();
cat.name = "小虎";
cat.age = 2;
cat.eat();
cat.mew();
}
}
1.1父类成员访问
1.1.1子类中访问父类的成员变量
1.子类和父类不存在同名成员变量
直接访问或者使用this方法。
2.子类和父类存在同名成员变量
用this访问子类变量,用super访问父类变量(super只能在非静态方法中使用)。
//Base.java
package demo;
public class Base {
public int a = 10;
public int b = 20;
}
//Derive.java
package demo;
public class Derive extends Base{
public int c = 30;
public int a = 100;
public void show(){
System.out.println(super.a);//父类中的a=10
System.out.println(this.a);//子类中的a=100
System.out.println(b);
System.out.println(c);
}
}
//Test.java
package demo;
public class Test {
public static void main(String[] args) {
Derive derive = new Derive();
derive.show();
}
}
//输出:10
// 100
// 20
// 30
在子类方法中或者通过子类对象访问成员时 :
- 如果访问的成员变量子类中有,优先访问自己的成员变量。
- 如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
- 如果访问的成员变量与父类中成员变量同名,则优先访问自己的。
- 成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找。
1.1.2子类中访问父类的成员方法
和访问成员一样。
1.2子类构造方法
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextInt()) {
int x = scanner.nextInt();
int y = scanner.nextInt();
int z = scanner.nextInt();
Sub sub = new Sub(x, y, z);
System.out.println(sub.calculate());
}
}
}
class Base {
private int x;
private int y;
public Base(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
class Sub extends Base {
private int z;
public Sub(int x, int y, int z) {
super(x,y);
this.z = z;
}
public int getZ() {
return z;
}
public int calculate() {
return super.getX() * super.getY() * this.getZ();
}
}
//输入:1 2 3
//输出:6
- 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构造方法。
- 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的 父类构造方法调用,否则编译失败。
- 在子类构造方法中,super(...)调用父类构造时,必须是子类构造函数中第一条语句。
- super(...)只能在子类构造方法中出现一次,并且不能和this同时出现。
- 构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有 。
1.3继承关系的执行顺序
先来看没有继承关系时的执行顺序:
class Person {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("构造方法执行");
}
{
System.out.println("实例代码块执行");
}
static {
System.out.println("静态代码块执行");
}
}
public class TestDemo {
public static void main(String[] args) {
Person person1 = new Person("bit",10);
System.out.println("============================");
Person person2 = new Person("gaobo",20);
}
}
//执行结果:
//静态代码块执行
//实例代码块执行
//构造方法执行
//============================
//实例代码块执行
//构造方法执行
- 静态代码块先执行,并且只执行一次,在类加载阶段执行
- 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行
有继承关系时的执行顺序:
class Person {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Person:构造方法执行");
}
{
System.out.println("Person:实例代码块执行");
}
static {
System.out.println("Person:静态代码块执行");
}
}
class Student extends Person{
public Student(String name,int age) {
super(name,age);
System.out.println("Student:构造方法执行");
}
{
System.out.println("Student:实例代码块执行");
}
static {
System.out.println("Student:静态代码块执行");
}
}
public class TestDemo4 {
public static void main(String[] args) {
Student student1 = new Student("张三",19);
System.out.println("===========================");
Student student2 = new Student("gaobo",20);
}
//执行结果:
//Person:静态代码块执行
//Student:静态代码块执行
//Person:实例代码块执行
//Person:构造方法执行
//Student:实例代码块执行
//Student:构造方法执行
//===========================
//Person:实例代码块执行
//Person:构造方法执行
//Student:实例代码块执行
//Student:构造方法执行
- 父类静态代码块优先于子类静态代码块执行,且是最早执行
- 父类实例代码块和父类构造方法紧接着执行
- 子类的实例代码块和子类构造方法紧接着再执行
- 第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行
1.4继承方式
继承关系一般不超过三层!
1.5final关键字
2.多态
多态是面向对象编程中的一个重要概念,它指的是同一个方法在不同的对象上具有不同的行为。多态性可以使得一个方法可以被不同类型的对象调用,从而实现不同的操作或者返回不同的结果。
实现条件:
- 必须在继承体系下
- 子类必须要对父类中方法进行重写
- 通过父类的引用调用重写的方法
下面是一个关于多态的简单例子:
假设有一个“动物”类(Animal)和它的两个子类,分别是“狗”类(Dog)和“猫”类(Cat)。每个类都有一个“叫声”(makeSound)的方法。可以通过多态来实现不同动物的叫声。
class Animal {
public void makeSound() {
System.out.println("动物发出叫声");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("狗发出汪汪叫声");
}
}
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("猫发出喵喵叫声");
}
}
public class PolymorphismExample {
public static void main(String[] args) {
Animal animal1 = new Dog();
Animal animal2 = new Cat();
animal1.makeSound(); // 输出:狗发出汪汪叫声
animal2.makeSound(); // 输出:猫发出喵喵叫声
}
}
在上面的例子中,通过将父类引用(Animal)指向子类的对象(Dog、Cat),实现了多态性。通过调用父类引用的同名方法(makeSound),根据实际对象类型确定调用的是子类的方法。
在这个例子中,无论是animal1还是animal2的类型都是Animal,但是实际指向的是Dog和Cat对象,所以调用makeSound方法时会根据实际对象类型来决定调用的方法。这样就实现了多态性,不同的动物对象调用makeSound方法时会有不同的叫声输出。
2.1重写
重写(Override)是面向对象编程中的一个概念,指的是在子类中重新定义父类中已经存在的方法,以便使得子类对象可以根据自己的特性来执行该方法。
简单来说,当子类中定义了与父类中同名、同参数列表、同返回类型的方法时,就称为方法的重写。重写的方法在子类中可以有不同的实现,即可以改变方法的行为,但不能改变方法的签名(方法名、参数列表和返回类型)。
重写的规则如下:
- 重写的方法必须和父类的方法具有相同的方法名、参数列表和返回类型。
- 重写的方法不能有更严格的访问权限,可以有更宽松的访问权限。例如,父类的方法是public,子类的重写方法可以是public或protected,但不能是private。
- 重写的方法不能抛出比父类方法更多的异常,可以抛出相同的异常或子类异常,或者不抛出异常。
- 当调用重写的方法时,实际执行的是子类中的方法,而不是父类中定义的方法。这个过程称为动态绑定。
重写的目的是为了实现多态,通过子类的不同实现来满足不同的需求。在实际使用中,可以根据具体的业务场景来决定是否需要重写某个方法。
2.2向上转型和向下转型
向上转型(upcasting)指的是将一个子类的对象赋值给一个父类的引用变量。这样做可以实现多态性,即通过父类引用变量调用子类对象的方法。
例如,假设有一个父类Animal和一个子类Dog,可以将一个Dog对象向上转型为Animal对象:
Animal animal = new Dog();
在这里,将Dog对象赋值给Animal引用变量,即实现了向上转型。
向下转型(downcasting)指的是将一个父类的引用变量赋值给一个子类的引用变量。在向上转型后,如果需要使用子类特有的方法或属性,可以通过向下转型实现。
例如,在上述例子中,可以使用向下转型将Animal引用变量转换为Dog对象:
Dog dog = (Dog) animal;
在这里,使用了强制类型转换符将Animal引用变量转换为Dog对象。
需要注意的是,向下转型可能会出现运行时异常,因为在向上转型后,父类引用变量只能访问父类的方法和属性,无法访问子类特有的方法和属性。如果在向下转型时将父类引用变量转换为错误的子类类型,会导致ClassCastException异常的抛出。因此,在向下转型前,最好使用instanceof运算符进行类型检查,以确保转型的安全性。
2.3多态的优缺点
多态的优点:
-
代码灵活性:多态允许使用父类引用变量来引用子类对象,这样可以实现更灵活的代码编写。通过多态,可以在不改变代码结构的情况下,通过改变对象的具体类型来修改程序的行为。
-
可扩展性:通过多态,可以轻松地扩展已有的代码。例如,可以通过添加新的子类来扩展已有的父类,而不需要修改原有的代码。
-
代码复用:多态可以在不重写代码的情况下,直接复用已有的父类代码。这样可以提高代码的可维护性和复用性。
多态的缺点:
-
性能损失:多态在运行时需要进行类型检查和动态绑定,这会引入一定的性能损失。相比于直接调用对象的方法,通过多态来调用方法会稍微慢一些。
-
可读性降低:多态会增加代码的复杂性,使得代码更难阅读和理解。当多个不同的子类实现了相同的方法时,需要通过查看父类的定义来确定具体会执行哪个子类的方法。
需要注意的是,多态并不适用于所有的情况。当需要调用特定子类的特定方法时,就不能使用多态。在这种情况下,可以通过向下转型来获得子类的引用,然后调用子类的方法。
2.4避免在构造方法中调用重写的方法
class B {
public B() {
// do nothing
func();
}
public void func() {
System.out.println("B.func()");
}
}
class D extends B {
private int num = 1;
@Override
public void func() {
System.out.println("D.func() " + num);
}
}
public class Test {
public static void main(String[] args) {
D d = new D();
}
}
// 执行结果:D.func() 0
- 构造 D 对象的同时, 会调用 B 的构造方法。
- B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func。
- 此时 D 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0. 如果具备多态性,num的值应该是1。
- 所以在构造函数内,尽量避免使用实例方法,除了final和private方法。
在构造方法中调用重写的方法可能会导致不可预知的行为。这是因为在对象构造期间,子类的构造方法会在父类的构造方法执行之前执行。因此,如果在父类的构造方法中调用了一个被子类重写的方法,那么实际调用的将是子类重写的方法。
为了避免这种情况,应该尽量避免在构造方法中调用重写的方法。如果确实需要在构造方法中执行某些逻辑,可以考虑将这些逻辑提取为一个独立的方法,在构造方法执行完毕后再调用该方法。这样可以保证对象的正确构造和初始化。