✨你好啊,我是“ 罗师傅”,是一名程序猿哦。
🌍主页链接:楚门的世界 - 一个热爱学习和运动的程序猿
☀️博文主更方向为:分享自己的快乐 briup-jp3-ing
❤️一个“不想让我曾没有做好的也成为你的遗憾”的博主。
💪很高兴与你相遇,一起加油!

前言

目标:Java基础编程,熟练Java开发语法和规则,养成良好编程习惯

static

问题引入

1
2
3
4
5
6
7
8
9
10
11
class School {
private String name; //名称
private int num; //师生数量
private String library; //图书馆
// 省略了get、set、构造器
}
public static void main(String[] args) {
School s1 = new School("第一中学",3000,"栋梁图书馆");
School s2 = new School("秀峰中学",2500,"栋梁图书馆");
School s3 = new School("娄江中学",1800,"栋梁图书馆");
}

private String library: 表示每个学校对象,都有自己独立的图书馆,与实际业务不符。

实际我们想要的效果为图书馆有且只有一个,只占用一块内存区域。

static是一个修饰符,表示静态的意思,可以修饰属性、方法、代码块

静态成员(静态变量)

static修饰类中的数据成员,该成员就成了静态数据成员,也称为类成员

类成员,是属于类的,为这个类所有对象共享,只占用一块内存空间。

static成员特点:

  • 被类的所有对象共享
  • 随着类的加载而加载,先于对象存在对象需要类被加载后,才能被创建出来
  • 可以通过类名调用,也可以通过对象名调用。但推荐使用类名类名.静态数据成员;
  • 方法区中有一块专门的区域:静态区,专门用来存储类的static成员

静态属性的存储位置:

在方法区中有一块静态区,专门用来存储各个类静态数据成员。当操作静态数据成员时,不论是通过类名,还是通过对象名操作,都是操作静态区中对应的内存区域

静态数据成员初始化

  • 静态数据成员随着类加载而加载(开辟相应内存),并会进行默认的初始化其值为null(类类型)或0(整形)或0.0(浮点型)或false(布尔类型)
  • 对静态数据成员的初始化,一般采用两种方式:显示初始化、静态代码块初始化
  • 显示初始化格式:[修饰符] static 数据类型 静态成员名 = 初始值; 例子:public static String library = "栋梁图书馆"

注意事项:尽量不要在构造方法中对static成员进行初始化,因为只要创建对象,构造方法就会自动被执行,导致static成员值不经意间被修改!

静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Demo{
//定义静态方法
public static void test() {
System.out.println("我是static静态方法...");
}
}
//测试方法
public static void main(String[] args){
//借助类名调用static方法,推荐方式
Demo.test();
Demo demo = new Demo();
//借助对象调用static方法,可以调用,但不推荐
demo.test();
}

static方法和普通成员方法区别:

  • 静态方法只能访问静态的成员
  • 非静态方法可以访问静态的成员,也可以访问非静态的成员
  • 静态方法中是没有this关键字(本质区别)

思考:为什么静态方法中不能访问普通数据成员?

静态方法也称为类方法,该类方法的调用不依赖对象,可以直接通过类名调用。

假设静态成员方法中能够访问普通数据成员,静态方法中有this引用,我 们来看下面场景:我们通过类名调用static方法,此时根本不会传递对象的 地址值给this引用,static方法内部也无法找到普通数据成员对应的内存空间 进行取值,这与我们要实现的功能是矛盾的。

故而,大家记住以下结论即可:静态只能访问静态

代码块

在java中,使用 { }括起来的代码被称为代码块,分为三类:

  • 局部代码块
    • 位置:类的方法中定义
    • 格式:{ 语句体; }
    • 作用:用户限定变量的生命周期,使其尽早释放,从而提高栈空间内存利用率
    • 重要程度:了解即可,使用不多
1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
//局部代码块
{
int a = 10;
System.out.println(a);
}
//上面局部代码块中的a是局部变量,遇到 } 其占用的内存空间就销毁了
//故而,下面输出语句编译报错
//System.out.println(a);
}
  • 构造代码块 (匿名代码块
    • 位置:类中方法外
    • 格式:{ 语句体; }
    • 特点:每次构造方法执行前,都会先执行该代码块中代码
    • 作用:如果多个构造方法中出现相同代码,可抽取到该快中,从而提高代码复用性。
    • 重要程度:掌握
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Module {
private int num;
{
System.out.println("构造代码块: 构造方法执行前执行...");
}

// 无参构造
public Module() {
System.out.println("Module...");
}

// 有参构造
public Module(int num) {
System.out.println("Module(int) ...");
this.num = num;
}
}

public static void main(String[] args) {
//实例化2个对象,观察程序输出效果:
//每次构造方法执行前,都会自动执行构造代码块
Module m1 = new Module();
Module m2 = new Module(10);
}
// 输出如下:
// 构造代码块: 构造方法执行前执行...
// Module...
// 构造代码块: 构造方法执行前执行...
// Module(int) ...
  • 静态代码块
    • 位置:类中方法外
    • 格式:static { 语句体; }
    • 特点:需要通过static关键字修饰,随着类的加载而加载,只执行一次
    • 作用:类加载时做一些数据的初始化操作(一般给static成员赋值)
    • 重要程度:掌握
1
2
3
4
5
6
7
8
//static成员第一种初始化方式:显式初始化
private static String library = "栋梁图书馆";

//static成员第二种初始化方式
static {
//library = "凌云图书馆";
System.out.println("static代码块执行...");
}

补充内容:static成员显示初始化和静态代码块,执行顺序问题

  • 情形一:把显式初始化放在静态代码块前面
  • 情形二:把静态代码块放在显式初始化前面

结论:显式初始化和静态代码块,谁在上面,谁先执行!

类加载时机

加载类的含义:JVM虚拟机将该类的class文件加载到内存的方法区中。

当Java代码中用到某个类的时候,就会加载该类。

加载时机(一共有七种):

  • 程序启动,找到main方法运行,此时用到main方法所属的类,应该加载该类。
1
2
3
4
5
6
//运行程序,先加载Test.class
public class Test {
public static void main(String[] args) {
//...
}
}
  • 第一次使用类去实例化对象时
    • 如: School s1 = new School();
  • 通过类名访问其静态数据成员
    • School.library = "凌志楼";
  • 通过类名调用其静态成员方法
    • School.setLibrary("逸夫楼");

对象创建过程

1
2
3
public static void main(String[] args) {
School s = new School("一中",3000);
}

请描述 School类对象s 的创建和初始化过程:

  • 对School类进行类加载
  • 方法区中的静态区为School.libary分配内存空间,并做默认初始化(null)
  • 对School.library(静态成员)进行显示初始化,执行School类中的静态代码块 (谁在上面,谁先执行!)
  • 栈空间(main方法函数帧)开辟一块内存,用 s(引用变量)标识
  • 堆区中分配对象的内存空间,同时进行默认初始化
  • 对School中的属性进行显式初始化 public int num = 2000;
  • 执行School类的构造代码块(=匿名代码块)
  • 执行School类的构造方法
  • 把对象堆空间的内存地址赋给变量s(写入s那块内存区域中)

至此:对象成功创建,并初始化成功啦~~

静态导入

JDK1.5中增加了静态导入的功能,具体描述如下:

  • 在自定义类中,要使用另一个类中的静态属性和静态方法,可以使用静态导 入。
  • 导入完成后,可以直接使用这个类中的静态属性和静态方法,而不用在前面 加上类名。

静态导入格式:

  • 导入静态成员: import static 类的全包名.static数据成员名;
  • 导入静态方法: import static 类的全包名.static成员方法名;
1
2
3
4
5
6
7
8
9
10
import static java.lang.Math.PI; // 导入后就不再需要使用类名.PI, 直接使用PI即可
import static java.lang.Math.random;
public class Test0107_StaticImport {
public static void main(String[] args) {
//访问Math类中的静态属性PI,表示圆周率π
System.out.println(PI);
//访问Math类中的静态方法random(),生成随机数
System.out.println(random());
}
}

继承

类和类之间的关系有很多中,继承就是其中一种关系,除此之外还有依赖、 组合、聚合

概念理解

继承是面向对象三大特征之一,可以有效的提高代码的复用性。

继承关系体现的是一种 “is a” 的关系!

蛇 is a 爬行动物,爬行动物是动物中的一种。

继承实现

固定格式:

1
2
3
4
5
6
[public] class 子类名 extends 父类名 {
子类新增内容;
}
// 例如
public class Animal{}
public Dog extends Animal{}

在继承关系中,父类,也称基类、超类子类,也称派生类。

继承特点

  • Java只支持单继承,不支持多继承
1
2
3
//编译报错,一个类只能有且只有一个父类,不能同时继承俩个父类
public class 儿子 extends 父1,父2 {
}
  • Java支持多层继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Grandpa {

public void drinking() {
System.out.println("爷爷喜欢喝茶");
}
}
public class Father extends Grandpa {

public void fishing() {
System.out.println("爸爸喜欢钓鱼");
}
}
public class Son extends Father {

public void programing() {
System.out.println("我喜欢敲代码");
}
}
public static void main(String[] args) {
Son son = new Son();
son.drinking();
son.fishing();
son.programing();
}

继承细节

子类只能继承父类所有非私有的成员(含成员方法和成员变量)

Java官方文档描述:从继承的概念来说,private修饰的成员不被继承

官网描述:

Inheritance (The Java™ Tutorials > Learning the Java Language > Interfaces and Inheritance) (oracle.com)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 父类 Animal
public class Animal extends Object {
private String color;
public int age;

public String getColor() {
return color;
}

public void setColor(String color) {
this.color = color;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public void show() {
System.out.println("Animal [color=" + color + ", age=" + age + "]");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 子类 Dog
public class Dog extends Animal {

// 父类中的private成员,子类中不可以直接操作(无直接访问权限)
// Animal: private String color;
// 父类中的非private成员,子类中可以直接操作
// Animal: public int age;
// 子类新增属性
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

// 子类新增方法:输出子类对象所有属性(含新增属性、从父类继承)
public void disp() {
// 子类方法中 不能直接操作 父类private成员
// System.out.println("继承 color: " + color); error
// 子类方法中 可以间接操作(借助public的get|set) 父类private成员
System.out.println("继承 color: " + getColor());
// 子类方法中 可以直接操作 父类非private成员
System.out.println("继承 age: " + age);
System.out.print("新增:" + name);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test0204_AccessMember {
public static void main(String[] args) {
Animal a = new Animal();
a.setColor("black");
a.setAge(3);
a.show();
System.out.println("------------");
Dog d = new Dog();
// 子类对象调用从父类继承的public方法
d.setColor("yellow");
d.setAge(2);
// 子类对象调用新增的方法
d.setName("虎子");
d.disp();
}
}

注意事项:

  • 从内存角度来分析,父类中的private成员,子类实际上也完全继承下来了(非专业描述)
  • 父类private成员为父类私有,在父类成员方法中可以直接访问,在其他类中(含子类),不能直接操作。
  • 子类中不能直接操作父类private成员,但是可以通过间接方式(比如借助父类public的get or set方法)操作

总结:子类继承了父类的所有成员,包括私有成员。但子类无法直接访问父类的私有成员,只能通过调用父类的公有方法或受保护方法来间接访问这些私有成员

额外补充:

Java中的类,如果类定义时没有指定父类,那么这个类会默认继承Object类!

1
2
//默认继承了Object,编译后会自动生成 extends Object 的代码语句
public class Animal {}

上图注意事项:

  • Object类中没有属性
  • 绿色圆点表示public修饰方法
  • 黄色菱形表示protected修饰方法
  • 红色方块表示private修饰方法

Java中的每个类都直接或间接继承Object类,Object类是Java继承体系中的最顶层父类。

应用场景

优缺点

优点:

  • 提高代码的复用性
  • 提高了代码的可维护性(父类修改后,子类全部生效)
  • 让类与类之间产生了 is a 的关系,是多态的前提

弊端:

  • 继承是侵入性
  • 继承让类与类之间产生了关系,类的耦合性增强了(代码与代码之间存在关联都可以将其称之为“耦合”)
  • 降低了代码的灵活性(当父类发生变化时子类实现也不得不跟着变化,削弱了子类的独立性

super

问题引入:

从父类继承的非private成员,和子类新增的成员重名,如何在子类成员方法中区分两个成员?(使用super)

super表示子类对象中从父类继承的那部分(可以看成一个父类对象)引用

1
2
3
4
5
public void memberAccess(int age) {
System.out.println("age: " + age); // 局部变量
System.out.println("this.age: " + this.age); // 当前对象的成员变量
System.out.println("super.age: " + super.age); // 父类的成员变量
}

在子类方法中访问一个变量,会根据就近原则

  • 先在子类局部范围中找局部变量
  • 再在子类新增的范围中查找 (类内,方法外:子类成员变量)
  • 最后从父类继承的成员范围中查找

如果一定要使用从父类继承的成员,可以通过super关键字,进行区分。

super总结:

  • super关键字的用户和this关键字的用法相似
  • this:代表本类对象的引用
  • super:代表父类存储空间的标识(可以理解为父类对象引用)

构造方法

思考:如何给子类对象进行初始化?

子类对象的数据成员包含两部分:继承部分,新增部分

  • 新增部分:子类构造方法中 this.新增数据成员 = 值
  • 对继承部分数据成员初始化
    • super(实际参数列表)
    • 子类构造方法前,会优先找到父类构造方法调用,对父类继承部分成员进行初始化
    • 父类部分初始化完成后,再执行子类构造方法代码
    • 如果子类构造方法中没有显示调用super(实参列表),则系统默认调用super()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//子类构造方法
public Zi() {
//如果不显式调用super,则默认调用父类无参构造器
//super();
System.out.println("in Zi() ...");
}

public Zi(int f, int z) { // 构
//下面这行注释的代码,放开则编译报错
//System.out.println("in Zi(int,int) ...");
//super调用,必须为子类构造方法的第一行有效代码
super(f);
System.out.println("in Zi(int,int) ...");
this.z = z;
}

// 新增方法
public void disp() {
//借助super可以直接访问父类继承部分的成员
System.out.println("super.f: " + super.getF());
//借助this,会先去找子类新增getF(),如果找不到,再去父类继承部分查找
System.out.println("this.f: " + this.getF());
System.out.println("Zi.z: " + z);
}

attention1:子类构造方法中如果显式调用super(实参列表), 则该代码必须为第一行有效代码!

attention2:子类构造方法中显式调用的super(实参列表),父类中必须提供,否则编译报错!

访问控制

概述:类中的属性和方法,可以使用以下四种权限修饰符进行访问控制:

public > protected > default(不写) > private

方法重写

  • 重写应用场景

父子类继承关系中,当子类需要父类的功能,而继承的方法不能完全满足子类的需求,子类里面有特殊的功能,此时可以重写父类中的方法,这样,即沿袭了 父类的功能,又定义了子类特有的内容。

  • 方法重写细节 (两同两小一大)
    • 两同:方法名相同,形参列表相同
    • 两小
      • 子类方法返回值类型应该比父类返回值类型更小或相等这个是对引用类型来锁的,基本数据类型返回值必须是相等的
      • 子类方法声明抛出的异常类型应比父方法声明抛出的异常类型更小或相等
    • 一大:子类方法的访问权限应比父类方法的访问权限更大或相等

结论:子类继承父类,在调用方法的时候,如果子类中没用重写,那么调用从父类继承的方法,如果子类重写了这个方法,那么调用到子类重写的方法。( 非常重要 )

特殊情况:

  • 父类中的静态方法(属于类方法)不能被子类重写(static也是父类私有的)
  • 父类中的私有方法(未被继承)不能被子类重写

重写Object类中toString()

  • Java中的类,如果类定义时没有指定父类,那么这个类默认继承Object类
  • Java中的每个类都直接或间接继承Object类,Object类是Java继承体系中的最顶层父类
1
2
3
4
5
6
7
public class Object{
public String toString() {
//返回 "类的全包名@该对象堆空间地址(十六进制形式)"
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
}
// com.briup.chap06.test.Game@659e0bfd

当输出对象时,会先执行 对象.toString() 获得对象的字符串形式,然后输出 该字符串!

1
2
3
4
5
6
// 重写toString方法
@Override
public String toString() {
return "Teacher [name=" + name + ", balance=" + balance + "]";
}
// Teacher [name=jack,balance=12345.6]

final

修饰类

用final修饰的类不能被继承,也就是说这个类是没有子类的。

1
2
public final class Animal{}
public class Dog extends Animal{} // 报错

JDK内置的类和大量框架类都有final修饰,不能进行重写。保护源码!

修饰方法

用final修饰的方法可以被子类继承,但是不能被子类的重写

1
2
3
4
5
6
7
8
9
public class Person {
public final void print() {}
}
//编译报错
class Student extends Person {
//从父类继承的final方法,不可以被重写
//public void print() {
//}
}

修饰变量

用final修饰的变量就变成了常量,并且它只能被赋一次值,第二次赋值就会 报错

  • final修饰局部变量
    • 则局部变量赋初值后,不能再次赋值,否则编译报错!
  • final修饰非静态成员变量

    • 显式初始化

    • 匿名代码块中初始化

    • 构造器中初始化

      注意:类中出现的所有构造器都要对final成员进行初始化,否则编译报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class F {
//第一种初始化方式:显式初始化
//private final int num = 10;

//第二种初始化方式:构造代码块初始化
// private final int num;
// {
// num = 20;
// }

//第三种初始化方式:所有构造器中都对final成员进行初始化
private final int num;
public F() {
//必须给num初始化
this.num = 30;
}
public F(int num) {
//必须给num初始化
this.num = num;
}
}
  • final修饰静态成员变量
    • 显式初始化:声明的同时赋值
    • 静态代码块中赋值
1
2
3
4
5
6
7
8
//第一种初始化方式:显式初始化
//private final static int num = 10;

//第二种初始化方式
private final static int num;
static {
num = 20;
}

修饰引用

final修饰引用类型变量时,则变量引用值不能改变,但是引用地址里面的内容是可以发生改变

多态

**多态(Polymorphism)**的字面意思为”一种事物,多种形态“。

Java多态理解: 引用变量.方法(实参列表) 完全相同的这行代码,出现在不同的位置,其执行的结果是不同的

即:同一父类的 不同子类 调用相同的方法 去做不同的事情

多态前提:

  • 子类继承父类
  • 子类重写父类中的方法
  • 父类的引用指向子类对象

注意,一个父类型的引用,可以指向它的任何一个子类对象

多态优缺点:

  • 优点
    • 提高程序的扩展性、灵活性
    • 定义方法时候,使用父类型作为参数,在使用的时候,使用具体的子类型参与操作(自动匹配)
  • 弊端
    • 不能使用子类的特有成员
  • 不使用多态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//篮球类
class BasketBall {
public void play() {
System.out.println("开始篮球游戏...");
}
}
//足球类
class Football {
public void play() {
System.out.println("开始足球游戏...");
}
}
//第一步:新增乒乓球类
//class PingPong {
// public void play() {
// System.out.println("开始乒乓球游戏...");
// }
//}

//定义游戏类
class Game {
//启动篮球游戏
public void start(BasketBall basketBall) {
basketBall.play();
}
//启动足球游戏
public void start(Football football) {
football.play();
}
//第二步:Game类新增重载start方法
// public void start(PingPong pingpong) {
// pingpong.play();
// }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//传统方式实现(非多态):代码的扩展性很差
public class Test08_Game {
public static void main(String[] args) {
//1.创建游戏对象
Game game = new Game();
//2.创建篮球对象,然后开始游戏
BasketBall basketBall = new BasketBall();
game.start(basketBall);
//3.创建足球对象,然后开始游戏
Football football = new Football();
game.start(football);
//第三步:测试类创建乒乓球对象,然后开始游戏
// PingPong pingpong = new PingPong();
// game.start(pingpong);
}
}

问题分析:如果后续要不断扩展游戏种类,则每次都要额外修改Game类中的代 码,这很不方便,且不安全(每次要改动之前的代码,容易出Bug),违反了开 闭原则

开闭原则(Open-Closed Principle,OCP)是面向对象设计中的一条基本原 则。指的是”软件实体(类、模块、函数等)应该对扩展开放、对修改关 闭”。 换句话说,当需求发生变化时,应该通过增加新的代码来扩展现有功能,而不是直接修改现有代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//抽取父类:球类
class Ball {
public void play() {}
}
//定义子类:篮球类
class BasketBall2 extends Ball {
//重写方法
public void play() {
System.out.println("开始篮球游戏...");
}
}
//定义子类:足球类
class Football2 extends Ball {
public void play() {
System.out.println("开始足球游戏...");
}
}
// 第一步:新增乒乓球子类
class PingPong2 extends Ball {
public void play() {
System.out.println("开始乒乓球游戏...");
}
}

// 定义游戏类
class Game2 {
//这里只需要定义一个方法即可,要求参数类型是父类Ball
//调用该方法时,需要传递一个该父类引用值,可以指向任何一个子类对象
public void start(Ball ball){
ball.play();
}
//Game类中代码不需要任何改动
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//多态实现:代码的扩展性很强
public class Test08_Game2 {
public static void main(String[] args) {
// 1.创建游戏对象
Game2 game = new Game2();
// 2.创建篮球对象,然后开始游戏
BasketBall2 basketBall = new BasketBall2();
game.start(basketBall);
// 3.创建足球对象,然后开始游戏
Football2 football = new Football2();
game.start(football);
// 第三步:测试类创建乒乓球对象,然后开始游戏
PingPong2 pingpong = new PingPong2();
game.start(pingpong);
}
}

用多态实现功能时,如果后续要不断扩展游戏种类,根本不需要修改Game类中的代码,符合开闭原则,大大提高了程序的扩展性和灵活性!

引用类型转换

  • 向上转型(隐式转换)
    • 父类引用指向子类对象,多态部分我们已经大量使用 Person p = new Student();
  • 向下转型(显式转换)
    • 子类引用指向父类对象
    • 格式:子类型 对象名 = (子类型)父类引用;
    • 前提:父类对象本身就是子类类型
      • Person p = new Student();
      • Student s = (Student)p; //向下转型

注意事项:先有向上转型,然后才能有向下转型

向上转型访问特点:(重点)

前提:使用父类引用指向子类对象,然后通过父类引用访问成员变量或成员方法

  • 操作成员变量:编译看左边(父类),运行看左边(父类)

在编译时,Java编译器只看左边(父类)的类型。这意味着如果你通过父类引用访问成员变量,编译器会检查这个成员变量在父类中是否存在,如果存在,则通过编译,否则报错。

在运行时,也是看左边(父类)的类型。这意味着无论实际对象是父类还是子类,都会访问父类中的成员变量。这是因为编译时已经确定了访问的是父类成员变量。

1
2
3
4
5
6
7
8
9
10
11
class Parent {
int x = 10;
}

class Child extends Parent {
int y = 20;
}

Parent parentRef = new Child();
System.out.println(parentRef.x); // 编译通过,输出 10
System.out.println(parentRef.y); // 编译错误,父类没有 y 成员变量
  • 操作成员方法:编译看左边(父类),运行看右边(子类)

在编译时,Java编译器只看左边(父类)的类型。这意味着如果你通过父类引用调用成员方法,编译器会检查这个方法在父类中是否存在,如果存在,则通过编译,否则报错。

在运行时,却是看右边(子类)的类型。这意味着无论实际对象是父类还是子类,都会调用子类中的成员方法。这是因为编译时只确定了调用的是父类中的方法,但实际运行时,实际对象是子类对象,所以会调用子类中的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Parent {
void display() {
System.out.println("Parent's display");
}
}

class Child extends Parent {
void display() {
System.out.println("Child's display");
}
}

Parent parentRef = new Child();
parentRef.display(); // 运行时输出 "Child's display"

向下转型功能测试:

注意:先有向上转型,然后才能有向下转型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test09_Trans {
// 注意事项:先有向上转型,然后才能有向下转型
public static void main(String[] args) {
//1.向上转型:用子对象 给 父类引用赋值
Base b = new Derived();
//父类中没有v这个成员,编译失败
//System.out.println(b.v); //error
//父类中没有disp()方法,编译失败
//b.disp(); //error
//2.借助向下转型可以解决上述问题
Derived d = (Derived)b;
//操作子类独有成员
System.out.println(d.v);
//操作子类独有方法
d.disp();
}
}

引用类型强换异常

在类型强制转换的过程中,可能会遇到类型转换异常。

1
2
3
4
5
6
7
//新增派生类
class Fork extends Base {
//新增独有方法
public void out() {
System.out.println("in Fork, n:" + n);
}
}
1
2
3
4
5
6
7
8
9
10
11
//测试代码
public class Test09_Trans {
public static void main(String[] args) {
//1.向上转型
Base b = new Derived();
//2.向下转型,思考:编译能否成功,运行能否成功?
Fork f = (Fork)b; // java.lang.ClassCastException
//3.调用独有方法
f.out();
}
}

报错分析:

如果被转的引用类型变量,对应的实际类型和目标类型不是同一种类型,那么在转换的时候就会出现ClassCastException异常

Fork f = (Fork)b; 强制转换时,b实际指向Derived对象,Derived和Fork类 型不是子父类关系,所以它是不能转为Fork对象的!

instanceof关键字

instanceof关键字能告诉我们,当前父类的引用,到底是执行的哪一个子类 对象

格式:

  • 引用名 instanceof 类型名

作用:

  • 判断左边引用名实际指向的对象,其所属类型是否为右边的类型名,返回boolean类型结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Animal {}

class Dog extends Animal {}

class Cat extends Animal {}

public class Main {
public static void main(String[] args) {
Animal animal1 = new Dog();
Animal animal2 = new Cat();

System.out.println(animal1 instanceof Dog); // 输出 true
System.out.println(animal1 instanceof Animal); // 输出 true
System.out.println(animal2 instanceof Dog); // 输出 false
System.out.println(animal2 instanceof Cat); // 输出 true
System.out.println(animal2 instanceof Animal); // 输出 true
}
}

❤️❤️❤️忙碌的敲代码也不要忘了浪漫鸭!

宝剑锋从磨砺出,梅花香自苦寒来。💪