Java 高级类特性
面向对象特征之二:继承
为什么要有继承?
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。
此处的多个类称为子类,单独的这个类称为父类(基类或超类)
继承的语法规则
1 | public Subclass extends Superclass{} |
作用
继承的出现提高了代码的复用性
继承的出现让类与类之间产生了关系,提供了多态的前提
不要仅为了获取其他类中某个功能而去继承
说明
子类继承了父类,就继承了父类的方法和属性。
在子类中,可以使用父类中定义的方法和属性,也可以创建新的数据和方法。
在 Java 中,继承的关键字用的是 extends
, 即子类不是父类的子集,而是对父类的扩展
关于继承的规则:
- 子类不能直接访问父类中私有的(private)成员变量和方法
- Java 只支持单继承,不允许多重继承
- 一个子类只能有一个父类
- 一个父类可以派生出多个子类
方法的重写
在子类中可以根据需要对从父类中继承来的方法进行改造,也称方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。子类重写父类的方法,只是重新编写方法体的代码。
要求:
重写方法必须和被重写方法具有相同的方法名称、参数列表和返回值类型
重写方法不能使用比被重写方法更严格的访问权限
重写方法和被重写的方法必须同时为
static
的,或者同时为非static
的子类方法抛出的异常不能大于父类被重写方法的异常
四种访问权限修饰符
如果子类和父类在同一个包下,那么对于父类的成员修饰符只要不是私有的 private
,子类那就可以使用
如果子类和父类不在同一个包中,那么子类只能使用父类中 protected
和 public
的成员变量
关键字 super
在 Java 类中使用 super
来调用父类中指定的操作:
super
可用于访问父类中定义的属性super
可用于调用父类中定义的成员方法super
可用于在子类构造方法中调用父类的构造器
注意
尤其是当子父类出现同名成员时,可以用 super
进行区分
super
的追溯不仅限于直接父类,使用 super
,子类可以调用子类之上的所有父类层级
super
和 this
的用法相像,this
代表本类对象的引用, super
代表父类的内存空间的标识
调用父类的构造器
子类中所有的构造器默认都会访问父类中空参数的构造器
当父类中没有空参数的构造器时,子类的构造器必须通过 this(参数列表)
或者 super(参数列表)
语句指定调用本类或者父类中相应的构造器,且必须放在构造器的第一行
如果子类构造器中既有显式调用父类或本类的构造器,且父类中又没有无参的构造器,则编译出错
this 和 super 的区别
区别点 | this | super |
---|---|---|
访问属性 | 访问本类中的属性,如果本类没有此属性则从父类中继续查找 | 访问父类中的属性 |
调用方法 | 访问本类中的方法 | 直接访问父类中的方法 |
调用构造器 | 调用本类构造器,必须放在构造器的首行 | 调用父类构造器,必须放在子类构造器的首行 |
特殊 | 表示当前对象 | 无此概念 |
子类对象实例化过程
简单类对象的实例化过程
- 在方法区加载
Person.class
- 在栈中申请空间,声明变量
p
- 在堆内存中开辟空间,分配地址,假设地址是
BE2500
- 在对象空间中(堆内存),对对象中的属性进行默认初始化,若对成员变量有赋值,则对成员变量显式初始化
- 构造函数方法进栈,进行初始化(栈内存)
- 初始化完毕后,将堆内存中的地址值赋给引用变量
p = BE2500
,构造方法出栈
子类对象的实例化过程
- 在方法区先加载
Person.class
, 再加载Student.class
- 在栈中申请空间,声明变量
stu
- 在堆内存中开辟空间,分配地址
- 并在对象空间中,对对象中的属性(包括父类的属性)进行默认初始化
- 子类构造方法进栈
- 显式初始化父类的属性(堆内存)
- 父类构造方法进栈,执行完毕出栈
- 显式初始化子类的属性(堆内存)
- 初始化完毕后,将堆内存中的地址值赋给引用变量
stu
. 子类构造方法出栈
面向对象特征之三:多态
多态性,是面向对象最重要的概念,在Java中有两种体现:
- 方法的重载(overload)和重写(overwrite)
- 对象的多态性——可以直接应用在抽象类和接口上
Java 引用变量有两个类型:编译时类型和运行时类型。
编译时类型由声明该变量时使用的类型决定,
运行时类型由实际赋给该变量的对象决定。
若编译时类型和运行时类型不一致,就出现多态(Polymorphism)——对象的多态
对象的多态 —— 在Java中,子类的对象可以代替父类的对象使用
- 一个变量只能有一种确定的数据类型
- 一个引用类型变量可能指向(引用)多种不同类型的对象
子类可以看做是特殊的父类,所以父类类型的引用可以指向子类的对象:向上转型
1 | class Person{} |
一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法。
1 | Student m = new Student(); |
属性是在编译时确定的,编译时 e
为 Person
类型,没有 school
成员变量,因而编译出错。
虚拟方法调用
正常的方法调用:
1 | Person p = new Person(); |
虚拟方法调用(多态情况下)
1 | Person e = new Student(); |
编译时类型和运行时类型:
编译时 e
为 Person
类,而方法的调用是在运行时确定的,所以调用的是 Student
类的 getInfo()
方法 —— 动态绑定
多态小结:
前提
需要存在继承或者实现关系
要有覆盖操作
成员方法:
- 编译时,要查看引用变量所属的类中是否有所调用的方法
- 运行时,要调用实际对象所属类中的重写方法
成员变量
不具备多态性,只看引用变量所属的类
instance of 操作符
x instance of A
: 检验 x
是否是类 A 的对象,返回值为 boolean
型
要求 x 所属的类与类 A 必须是子类和父类的关系,否则编译出错
如果 x 属于类 A 的子类 B ,x instance of A
值也为 true
Object类,包装类
Object 类
Object 类是所有Java类的根父类 —— 基类
object类中的主要方法:
1 | public Object() //构造方法 |
对象类型转换
基本数据类型转换的 Casting
自动类型转换:小的数据类型可以自动转换成大的数据类型
long g = 20;
double d = 12.0f
强制类型转换:可以把大的数据类型转换成小的数据类型
float f = (float)12.0;
int a = (int) 1200L
对Java对象的强制类型转换称为造型
- 从子类到父类的类型转换可以自动进行
- 从父类到子类的类型转换必须通过造型(强制类型转换)实现
- 无继承关系的引用类型间的转换是非法的
==
操作符与 equals
方法
==
基本类型比较值:只要两个变量的值相等,即为 true
int a = 5;
if(a==6){...};
引用类型比较引用(是否指向同一个对象):只有指向同一个对象时,
==
才返回true1
2
3Person p1 = new Person();
Person p2 = new Person();
if(p1 == p2){...}用
==
进行比较时,符号两边的数据类型必须兼容(可自动转换的基本数据类型除外),否则编译出错
equals
所有类都继承了 Object,也就获得了equals()
方法。还可以重写。- 只能比较引用类型,其作用与
==
相同,比较是否指向同一个对象。- 格式:
obj1.equals(obj2)
- 格式:
- 特例:当用
equals()
方法进行比较时,对类 File、String、Date 及包装类(Wrapper)来说,是比较类型及内容而不考虑引用的是否是同一个对象- 原因:在职这些类中重写了 Object 类的
equals()
方法
- 原因:在职这些类中重写了 Object 类的
总结
对于对象来说,特殊的类,如 String、File、Date使用
==
比较的是对象(对象的地址),equals
比较的是内容除了特殊类之外的其他其他普通对象,
==
和equals
比较的都是对象(对象的内存地址)- 只能比较引用类型,其作用与
String 对象的创建
字面量创建 String 对象
1
2
3String s1 = "abc"; // 堆内存的常量池中添加“abc”对象,返回引用地址给s1对象
String s2 = "abc"; // 通过 equals() 方法判断常量池中是否存在值为“abc”的对象,返回相同的引用
System.out.println(s1==s2); // true, s1==s2new 创建 String 对象
1
2
3String s3 = new String("def"); // 在常量池中添加“def”对象,在堆中创建值为“def"的对象s3,返回指向堆中s3的引用
String s4 = new String("def"); // 常量池中已有“def”对象,不做处理,在堆中创建值为“def”的对象s4,返回指向堆中s4的引用
// 因此 s3 和 s4 指向的不是一个对象字面量创建对象的时候,只在常量池创建一个对象。
使用
new
创建对象,常量池有对象,堆中也要有对象,字面量方法要比new
方法省内存字面量相加
1
String s5 = "x"+"y"; // 经过JVM的优化,直接在常量池中添加“xy”对象
通过
new
的方式字符串叠加1
String s6 = new String("1")+new String("1")+new String("2"); // 通过StringBuilder实现,在常量池中添加“1”和“2”两个对象,在堆中创建值为“112”的对象,把引用地址给s6
包装类(Wrapper)
针对八种基本类型定义相应的引用类型——包装类(封装类)
基本数据类型 | 包装类 |
---|---|
boolean | Boolean |
byte | Byte |
short | Short |
int | Integer |
long | Long |
char | Character |
float | Float |
double | Double |
基本数据类型包装成包装类的实例——装箱
通过包装类的构造器实现:
1
2int i = 500;
Interger t = new Integer(i);还可以通过字符串参数构造包装类对象:
1
2Float f = new float("4.56");
Long l = new Long("asdf"); // NumberFormatException获得包装类对象中包装的基本数据类型变量——拆箱
调用包装类的
.xxxValue()
方法:boolean b = bObj.booleanValue();
JDK1.5后支持自动装箱和拆箱:
1 | Integer l1 = 112; // 自动装箱 |
字符串转换成基本数据类型
通过包装类的构造器实现:
int i = new Integer("12");
通过包装类的
parseXxx(String s)
静态方法:Float f = Float.parseFloat("12.1");
基本数据类型转换成字符串
调用字符串重载的
valueOf()
方法String fstr = String.valueOf(2.34f);
更直接的方式
String intStr = 5 + ""
包装类的作用
基本数据类型的包装类实现了基本数据类型与字符串直接转化
包装类使得一个基本数据类型的数据变成了类。有了类的特点,可以调用类中的方法。
- 本文作者: Kelly Liu
- 本文链接: http://tiantianliu2018.github.io/2019/10/03/Java-高级类特性/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!