1. 关键字:this
1. 1 this是什么?
- 在Java中,this关键字不算难理解,它的作用和其词义很接近。
- 它在方法(准确的说是实例方法或非static的方法)内部使用,表示调用该方法的对象
- 它在构造器内部使用,表示该构造器正在初始化的对象。
- this可以调用的结构:成员变量、方法和构造器
1. 2 目前可能出现的问题?及解决方案?
我们在声明一个属性对应的setXxx方法时,通过形参给对应的属性赋值。
如果形参和属性名同名了,如何在方法内区分这两个变量?
解决方案:使用this
使用this修饰的变量,表示的是属性,没有用this修饰的,表示的是形参
1. 3 this可以调用的结构:
成员变量、方法、构造器
1. 4 this的理解
当前对象(在方法中调用时)或 当前正在创建的对象 (在构造器中调用时)
1. 5 this调用属性和方法
【针对于方法内的使用情况 (非static修饰的方法)】
- 一般情况:通过对象调用方法,可以在方法内调用当前对象的属性或其他方法。
此时,可以在属性和其他方法前使用 this.
表示当前属性或其他方法,但是一般省略。
- 特殊情况:如果方法的形参和对象的属性重名了,必须使用
this.
【针对于构造器内的使用情况】
- 一般情况:通过构造器创建对象时,可以在构造器内调用当前对象的属性或其他方法。
此时,可以在属性和方法前使用 this.
表示当前属性或方法所属的对象,但是一般省略。
- 特殊情况:如果构造器的形参和正在创建的对象的属性重名了,必须使用
this.
1. 6 this调用构造器
- 格式:使用
this(形参列表)
- 调用当前类中的其他构造器
- 要求:必须声明在首行
- 结论:
this()
在构造器中最多声明一个 - 如果一个类中声明了n个构造器,则最多有n-1个构造器可以声明有
this(形参列表)
的结构,避免造成递归一直调用自己
案例
2. 面向对象特征二:继承(Inheritance)
1. Java中的继承
角度一:从上而下
定义了一个类A,在定义另一个类B时,发现类B的功能与类A相似,考虑类B继承于类A
为描述和处理个人信息,定义类Person:
为描述和处理学生信息,定义类Student:
通过继承,简化Student类的定义:
说明:Student类继承了父类Person的所有属性和方法,并增加了一个属性school。Person中的属性和方法,Student都可以使用。
角度二:从下而上
定义了类B,C,D等,发现B、C、D有类似的属性和方法,则可以考虑将相同的属性和方法进行抽取,
封装到类A中,让类B、C、D继承于类A,同时,B、C、D中的相似的功能就可以删除了。
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类中无需再定义这些属性和行为,只需要和抽取出来的类构成继承关系
。如图所示:
1.2 继承性的好处
- 继承的出现减少了代码冗余,提高了代码的复用性。
- 继承的出现,更有利于功能的扩展。
- 继承的出现让类与类之间产生了
is-a
的关系,为多态的使用提供了前提。 - 继承描述事物之间的所属关系,这种关系是:
is-a
的关系。可见,父类更通用、更一般,子类更具体。
2. 继承的语法
2.1 继承中的语法格式
通过 extends 关键字,可以声明一个类B继承另外一个类A,定义格式如下:
[修饰符] class 类A {
...
}
[修饰符] class 类B extends 类A {
...
}
2.2 继承中的基本概念
类B,称为子类、派生类(derived class)、SubClass
类A,称为父类、超类、基类(base class)、SuperClass
2.3 有了继承性以后:
-
子类就获取到了父类中声明的所有的属性和方法。
-
但是,由于封装性的影响,可能子类不能直接调用父类中声明的属性或方法。
- 可以通过get set方法调用
- 父类写
get set
方法 - 子类调用
getAge()
- 父类写
- 可以通过get set方法调用
-
子类在继承父类以后,还可以扩展自己特有的功能(体现:增加特有的属性、方法)
子类和父类的理解,要区别于集合和子集
> 不要为了继承而继承。在继承之前,判断一下是否有is a的关系。
1、子类会继承父类所有的实例变量和实例方法
验证
2、子类不能直接访问父类中私有的(private)的成员变量和方法
子类虽会继承父类私有(private)的成员变量,但子类不能对继承的私有成员变量直接进行访问,可通过继承的get/set方法进行访问。如图所示:
而在Student类中不能直接调用Person类中的age
如果需要调用age这个属性 需要在父类添加getset方法
在子类直接使用getAge
3、在Java 中,继承的关键字用的是“extends”,即子类不是父类的子集,而是对父类的“扩展”
子类在继承父类以后,还可以定义自己特有的方法,这就可以看做是对父类功能上的扩展。
4、Java支持多层继承(继承体系)
如:Student
类继承Person
类,Person
类继承Object
类Student
类和Object
类的关系 间接父类关系
class A{}
class B extends A{}
class C extends B{}
5、一个父类可以同时拥有多个子类
class A{}
class B extends A{}
class D extends A{}
class E extends A{}
6、Java只支持单继承,不支持多重继承
Java中一个父类可以声明多个子类。反之,一个子类只能有一个父类(Java的单继承性)
public class A{}
class B extends A{}
//一个类只能有一个父类,不可以有多个直接父类。
class C extends B{} //ok
class C extends A,B... //error
2.4 获取所属的类和父类
3. 方法的重写(override/overwrite)
父类的所有方法子类都会继承,但是当某个方法被继承到子类之后,子类觉得父类原来的实现不适合于自己当前的类,该怎么办呢?子类可以对从父类中继承来的方法进行改造,我们称为方法的重写 (override、overwrite)。也称为方法的重置、覆盖。
在程序执行时,子类的方法将覆盖父类的方法。
3.1 为什么需要方法的重写?
子类在继承父类以后,就获取了父类中声明的所有的方法。
但是,父类中的方法可能不太适用于子类,换句话说,子类需要对父类中继承过来的方法进行覆盖、覆写的操作。
3.2 何为方法的重写?
子类对父类继承过来的方法进行的覆盖、覆写的操作,就称为方法的重写。
3.3 方法重写应遵循的规则
[复习]方法声明的格式:权限修饰符 返回值类型 方法名(形参列表) [throws 异常类型] { //方法体 }
具体规则:
① 父类被重写的方法与子类重写的方法的方法名和形参列表必须相同。
② 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符
- 子类不能重写父类中声明为private权限修饰的方法。
③ 关于返回值类型:同返回
- 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型必须是void
- 父类被重写的方法的返回值类型是基本数据类型,则子类重写的方法的返回值类型必须与被重写的方法的返回值类型相同。
- 特殊:父类被重写的方法的返回值类型是引用数据类型(比如类),则子类重写的方法的返回值类型可以与被重写的方法的返回值类型相同 或 是被重写的方法的返回值类型的子类
- 即除了引用类型以外都是同返回,而引用类型除了一样 还可以是父类的子类类型
④ (超纲)子类重写的方法抛出的异常类型可以与父类被重写的方法抛出的异常类型相同,或是父类被重写的方法抛出的异常类型的子类。
补充说明:方法体:没有要求。但是子类重写的方法的方法体必然与父类被重写的方法的不同。
方法重写规则小结
- 子类重写的方法
必须
和父类被重写的方法具有相同的方法名称
、参数列表
。 - 子类重写的方法的返回值类型
不能大于
父类被重写的方法的返回值类型。(例如:Student < Person)。
注意:如果返回值类型是基本数据类型和void,那么必须是相同
- 子类重写的方法使用的访问权限
不能小于
父类被重写的方法的访问权限。(public > protected > 缺省 > private)
注意:① 父类私有方法不能重写 ② 跨包的父类缺省的方法也不能重写
- 子类方法抛出的异常不能大于父类被重写方法的异常
3.4 小结:方法的重载与重写
方法的重载:方法名相同,形参列表不同。不看返回值类型。
方法的重写:继承以后,子类覆盖父类中同名同参数的方法。
4. 关键字:super
4.1 super的理解
在Java类中使用super来调用父类中的指定操作:
- super可用于访问父类中定义的属性
- super可用于调用父类中定义的成员方法
- super可用于在子类构造器中调用父类的构造器
注意:
- 尤其当子父类出现同名成员时,可以用super表明调用的是父类中的成员
- super的追溯不仅限于直接父类
- super和this的用法相像,this代表本类对象的引用,super代表父类的内存空间的标识
4.2 为什么需要super?
举例1:子类继承父类以后,对父类的方法进行了重写,那么在子类中,是否还可以对父类中被重写的方法进行调用?
可以!
如何调用? 使用super关键字即可。
举例2:子类继承父类以后,发现子类和父类中定义了同名的属性,是否可以在子类中区分两个同名的属性?
可以!
就近原则 先在本类找 没有在父类找
4.3 super的理解:父类的
4.4 super可以调用的结构:属性、方法、构造器
具体的:
4.4.1 super调用属性、方法
子类继承父类以后,我们就可以在子类的方法或构造器中,调用父类中声明的属性或方法。(满足封装性的前提下)
如何调用呢?需要使用"super."的结构,表示调用父类的属性或方法。
一般情况下,我们可以考虑省略"super."的结构。但是,如果出现子类重写了父类的方法或子父类中出现了同名的属性时,
则必须使用"super."的声明,显式的调用父类被重写的方法或父类中声明的同名的属性。
4.4.2 super调用构造器
① 子类继承父类时,不会继承父类的构造器。只能通过“super(形参列表)”的方式调用父类指定的构造器。
② 规定:“super(形参列表)”,必须声明在构造器的首行。
③ 我们前面讲过,在构造器的首行可以使用"this(形参列表)“,调用本类中重载的构造器,
结合②,结论:在构造器的首行,“this(形参列表)” 和 “super(形参列表)“只能二选一。
④ 如果在子类构造器的首行既没有显示调用"this(形参列表)”,也没有显式调用"super(形参列表)”,
则子类此构造器默认调用"super()”,即调用父类中空参的构造器。
⑤ 由③和④得到结论:子类的任何一个构造器中,要么会调用本类中重载的构造器,要么会调用父类的构造器。
只能是这两种情况之一。
⑥ 由⑤得到:一个类中声明有n个构造器,最多有n-1个构造器中使用了"this(形参列表)“,
则剩下的那个一定使用"super(形参列表)”。
4.4.4调用父类构造器的目的
我们在通过子类的构造器创建对象时,一定在调用子类构造器的过程中,直接或间接的调用到父类的构造器。
也正因为调用过父类的构造器,我们才会将父类中声明的属性或方法加载到内存中,供子类对象使用。
子类调用父类构造器
5. 子类对象实例化全过程
代码举例:
class Creature{ //生物类
//声明属性、方法、构造器
}
class Animal extends Creature{ //动物类
}
class Dog extends Animal{ //狗类
}
class DogTest{
public static void main(String[] args){
Dog dog = new Dog();
dog.xxx();
dog.yyy = ...;
}
}
1. 从结果的角度来看:体现为类的继承性
当我们创建子类对象后,子类对象就获取了其父类中声明的所有的属性和方法,
在权限允许的情况下,可以直接调用。
2. 从过程的角度来看:
当我们通过子类的构造器创建对象时,子类的构造器一定会直接或间接的调用到其父类的构造器,
而其父类的构造器同样会直接或间接的调用到其父类的父类的构造器,…,直到调用了Object类中的构造器为止。
正因为我们调用过子类所有的父类的构造器,所以我们就会将父类中声明的属性、方法加载到内存中,供子类的对象使用。
问题:在创建子类对象的过程中,一定会调用父类中的构造器吗? yes!
3. 问题:创建子类的对象时,内存中到底有几个对象?
就只有一个对象!即为当前new后面构造器对应的类的对象。
6. 面向对象特征三:多态性
1. 如何理解多态性?
理解:理解为一个事物的多种形态。
生活举例:
- 女朋友:我想养一个宠物。
- 孩子:我想要一个玩具。
- 老板:张秘书,安排一个技术科的同事,跟我一起下周出差。
2. Java中多态性的体现:
子类对象的多态性:父类的引用 指向 子类的对象。(或子类的对象赋给父类的引用)
比如:Person p2 = new Man();
3. 多态性的应用:
多态性的应用:虚拟方法调用
在多态的场景下,调用方法时。简称为:编译看左边,运行看右边。
编译时,认为方法是左边声明的父类的类型的方法(即被重写的方法)
执行式,实际执行的是子类重写父类的方法。
4. 多态性的使用前提:
① 要有类的继承关系
② 要有方法的重写
5. 多态的适用性:
适用于方法,不适用于属性。
6. 为什么需要多态性(polymorphism)?
开发中,有时我们在设计一个数组、或一个成员变量、或一个方法的形参、返回值类型时,无法确定它具体的类型,只能确定它是某个系列的类型。
案例:
(1)声明一个Dog类,包含public void eat()方法,输出“狗啃骨头”
(2)声明一个Cat类,包含public void eat()方法,输出“猫吃鱼仔”
(3)声明一个Person类,功能如下:
- 包含宠物属性
- 包含领养宠物方法 public void adopt(宠物类型Pet)
- 包含喂宠物吃东西的方法 public void feed(),实现为调用宠物对象.eat()方法
7. 多态的好处与弊端
7.1 弊端:
在多态的场景下,我们创建了子类的对象,也加载了子类特有的属性和方法。但是由于声明为父类的引用,
导致我们没有办法直接调用子类特有的属性和方法。
7.2 好处:
极大的减少了代码的冗余,不需要定义多个重载的方法。
class Account{
public void withdraw(){} //取钱
}
class CheckAccount extends Account{ //信用卡
//存在方法的重写
public void withdraw(){} //取钱
}
class SavingAccount extends Account{ //储蓄卡
//存在方法的重写
}
class Customer{
Account account;
public void setAccount(Account account){
this.account = account;
}
public Account getAccount(){
return accout;
}
}
class CustomerTest{
main(){
Customer cust = new Customer();
cust.setAccount(new CheckAccount());
cust.getAccount().withdraw();
}
}
8. 向上转型与向下转型
首先,一个对象在new的时候创建是哪个类型的对象,它从头至尾都不会变。即这个对象的运行时类型,本质的类型用于不会变。但是,把这个对象赋值给不同类型的变量时,这些变量的编译时类型却不同。
向上转型:
当左边的变量的类型(父类) > 右边对象/变量的类型(子类),我们就称为向上转型
- 此时,编译时按照左边变量的类型处理,就只能调用父类中有的变量和方法,不能调用子类特有的变量和方法了
- 但是,运行时,仍然是对象本身的类型,所以执行的方法是子类重写的方法体。
- 此时,一定是安全的,而且也是自动完成的
向下转型:使用强转符号
当左边的变量的类型(子类)< 右边对象/变量的编译时类型(父类),我们就称为向下转型
- 此时,编译时按照左边变量的类型处理,就可以调用子类特有的变量和方法了
- 但是,运行时,仍然是对象本身的类型
- 不是所有通过编译的向下转型都是正确的,可能会发生ClassCastException,为了安全,可以通过isInstanceof关键字进行判断
8.1 为什么要类型转换
因为多态,就一定会有把子类对象赋值给父类变量的时候,这个时候,在编译期间,就会出现类型转换的现象。
但是,使用父类变量接收了子类对象之后,我们就不能调用子类拥有,而父类没有的方法了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做类型转换,使得编译通过。
9.向下转型可能会出现的问题
如何避免?
建议在向下转型之前,使用instanceof进行判断,避免出现类型转换异常
格式: a instanceOf A : 判断对象a是否是类A的实例。
如果a instanceOf A 返回true,则:a instanceOf superA 返回也是true。其中,A 是superA的子类。
练习
测试类
练习 向下转型
2、在包中声明测试类Exer4
1)public static void meeting(Person... ps)
在该方法中,每一个人先吃饭,然后上洗手间,然后如果是男人,随后抽根烟;如果是女人,随后化个妆
(2)public static void main(String[] args)
在主方法中,创建多个男人和女人对象,并调用meeting()方法进行测试
12 新特性:
注意
instanceof 后面写父类类型 也不会报错
但是String 编译都不会过 因为跟Person类没有任何关系
面试题
属性不存在多态性
多态是编译时行为还是运行时行为?
运行时行为
7. Object
1. equals()
比较两个对象的地址是否相同
此时点进方法是在Object类中
public boolean equals(Object obj) {
return (this == obj);
}
1.2 比较两个String
此时点进方法是在String类中
其中
- 先看地址是否一样
- 如果地址不一样,判断是不是String类,
- 如果是String,强转成String类型
- 字符串底层就是char类型的数组
- 先比较两个字符串的长度否一样
- 如果长度一样,一个一个字符比较
1.3 比较两个文件
1.4 总结:
-
自定义的类在没有重写Object中equals()方法的情况下,调用的就是Object类中声明的equals(),比较两个
对象的引用地址是否相同。(或比较两个对象是否指向了堆空间中的同一个对象实体)
-
对于像String、File、Date和包装类等,它们都重写了Object类中的equals()方法,用于比较两个对象的
实体内容是否相等。
1.5 重写比较两个对象内容
1.6 IDEA自动生成equals
1.7 == 和 equals 区别
==:运算符
①使用范围:基本数据类型、引用数据类型
② 基本数据类型:判断数据值是否相等
③ 引用数据类型变量:比较两个引用变量的地址值是否相等。(或比较两个引用是否指向同一个对象实体)
equals():方法
- 使用范围:只能使用在引用数据类型上。
- 具体使用:对于类来说,重写equals()和不重写equals()的区别。
2. toString()
2.1 Object类中toString()的定义:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
2.2 开发中的使用场景
平时我们在调用System.out.println()打印对象引用变量时,其实就调用了对象的toString()
2.3 子类使用说明:
- 自定义的类,在没有重写Object类的toString()的情况下,默认返回的是当前对象的地址值。
- 像String、File、Date或包装类等Object的子类,它们都重写了Object类的toString(),在调用toString()时,返回当前对象的实体内容。