1. this 关键字
1.1 this 的含义
- 在 Java 中,this 关键字不算难理解,它的作用和其词义很接近。
- 它在方法(准确的说是实例方法或非 static 的方法)内部使用,表示调用该方法的对象
- 它在构造器内部使用,表示该构造器正在初始化的对象。
- this 可以调用的结构:成员变量、方法和构造器
1.2 什么时候用 this
- 实例方法或构造器中使用当前对象的成员 使用 this 来区分成员变量和局部变量
- 同一个类中构造器互相调用
this 可以作为一个类中构造器相互调用的特殊格式。
- this():调用本类的无参构造器
- this(实参列表):调用本类的有参构造器
注意:
- 不能出现递归调用。比如,调用自身构造器。
- 推论:如果一个类中声明了 n 个构造器,则最多有 n - 1 个构造器中使用了"this(形参列表)"
- this()和 this(实参列表)只能声明在构造器首行。
- 推论:在类的一个构造器中,最多只能声明一个"this(参数列表)"
2. 面向对象特征二 继承(Inheritance)
2.1 继承性的理解
生活上:财产的继承、颜值的继承
代码层面:
- 自上而下:定义了一个类 A,在定义另一个类 B 时,发现类 B 的功能与类 A 相似,考虑类 B 继承于类 A
- 自下而上:定义了类 B,C,D 等,发现 B、C、D 有类似的属性和方法,则可以考虑将相同的属性和方法进行抽取,封装到类 A 中,让类 B、C、D 继承于类 A,同时,B、C、D 中的相似的功能就可以删除了。
有了继承性以后:
- 子类就获取到了父类中声明的所有的属性和方法。
- 但是,由于封装性的影响,可能子类不能直接调用父类中声明的属性或方法。
- 子类在继承父类以后,还可以扩展自己特有的功能(体现:增加特有的属性、方法)
- extends:延展、扩展、延伸
- 子类和父类的理解,要区别于集合和子集
- 不要为了继承而继承。在继承之前,判断一下是否有 is a 的关系。
默认的父类:
Java 中声明的类,如果没有显式的声明其父类时,则默认继承于 java.lang.Object
补充说明:
- Java 是支持多层继承。
- 概念:直接父类、间接父类
- Java 中的子父类的概念是相对的。
- Java 中一个父类可以声明多个子类。反之,一个子类只能有一个父类(Java 的单继承性)
2.2 继承性的好处
- 继承的出现减少了代码冗余,提高了代码的复用性。
- 继承的出现,更有利于功能的扩展。
- 继承的出现让类与类之间产生了
is-a
的关系,为多态的使用提供了前提。- 继承描述事物之间的所属关系,这种关系是:
is-a
的关系。可见,父类更通用、更一般,子类更具体。
- 继承描述事物之间的所属关系,这种关系是:
2.3 继承的格式
代码语言:javascript复制class A{
//属性、方法
}
class B extends A{
}
继承中的基本概念:
- 类 A: 父类、superClass、超类、基类
- 类 B: 子类、subClass、派生类
3. 方法的重写
3.1 方法重写的要求
- 子类重写的方法
必须
和父类被重写的方法具有相同的方法名称
、参数列表
。 - 子类重写的方法的返回值类型
不能大于
父类被重写的方法的返回值类型。(例如:Student < Person)。
注意:如果返回值类型是基本数据类型和 void,那么必须是相同
- 子类重写的方法使用的访问权限
不能小于
父类被重写的方法的访问权限。(public > protected > 缺省 > private)
注意:① 父类私有方法不能重写 ② 跨包的父类缺省的方法也不能重写
- 子类方法抛出的异常不能大于父类被重写方法的异常
此外,子类与父类中同名同参数的方法必须同时声明为非 static 的(即为重写),或者同时声明为 static 的(不是重写)。因为 static 方法是属于类的,子类无法覆盖父类的方法。
3.2 为什么需要方法的重写?
子类在继承父类以后,就获取了父类中声明的所有的方法。但是,父类中的方法可能不太适用于子类,换句话说,子类 需要对父类中继承过来的方法进行覆盖、覆写的操作。
举例(银行账户):
代码语言:javascript复制class Account{//账户
double balance;//余额
//取钱
public void withdraw(double amt){
//判断balance余额是否够amt取钱的额度
}
}
class CheckAccount extends Account{ //信用卡
double protectedBy;//透支额度
public void withdraw(double amt){
//判断balance余额是否够amt取钱的额度
//如果不够,还可以考虑从protectedBy额度里取
}
}
class AccountTest{
public static void main(String[] args){
CheckAccount acct = new CheckAccount();
acct.withdraw(); //执行的是子类重写父类的方法
}
}
3.3 区分方法的重载(overload)与重写(override / overwrite)
重载:“两同一不同”
重写:继承以后,子类覆盖父类中同名同参数的方法
4. 关键字 super
super 的理解:父类的
在 Java 类中使用 super 来调用父类中的指定操作:
- super 可用于访问父类中定义的属性
- super 可用于调用父类中定义的成员方法
- super 可用于在子类构造器中调用父类的构造器
注意:
- 尤其当子父类出现同名成员时,可以用 super 表明调用的是父类中的成员
- super 的追溯不仅限于直接父类
- super 和 this 的用法相像,this 代表本类对象的引用,super 代表父类的内存空间的标识
4.1 为什么需要 super?
- 举例 1:子类继承父类以后,对父类的方法进行了重写,那么在子类中,是否还可以对父类中被重写的方法进行调用? 可以!
- 举例 2:子类继承父类以后,发现子类和父类中定义了同名的属性,是否可以在子类中区分两个同名的属性? 可以!
4.2 如何调用?
使用 super 关键字即可。
4.3 super 调用构造器
① 子类继承父类时,不会继承父类的构造器。只能通过“super(形参列表)”的方式调用父类指定的构造器。
② 规定:“super(形参列表)”,必须声明在构造器的首行。
③ 我们前面讲过,在构造器的首行可以使用"this(形参列表)",调用本类中重载的构造器, 结合 ②,结论:在构造器的首行,"this(形参列表)" 和 "super(形参列表)"只能二选一。
④ 如果在子类构造器的首行既没有显示调用"this(形参列表)",也没有显式调用"super(形参列表)", 则子类此构造器默认调用"super()",即调用父类中空参的构造器。
⑤ 由 ③ 和 ④ 得到结论:子类的任何一个构造器中,要么会调用本类中重载的构造器,要么会调用父类的构造器。 只能是这两种情况之一。
⑥ 由 ⑤ 得到:一个类中声明有 n 个构造器,最多有 n-1 个构造器中使用了"this(形参列表)", 则剩下的那个一定使用"super(形参列表)"。
我们在通过子类的构造器创建对象时,一定在调用子类构造器的过程中,直接或间接的调用到父类的构造器。 也正因为调用过父类的构造器,我们才会将父类中声明的属性或方法加载到内存中,供子类对象使用。
4.4 super 调用属性、方法
子类继承父类以后,我们就可以在子类的方法或构造器中,调用父类中声明的属性或方法。(满足封装性的前提下)
如何调用呢?需要使用"super."的结构,表示调用父类的属性或方法。
一般情况下,我们可以考虑省略"super."的结构。但是,如果出现子类重写了父类的方法或子父类中出现了同名的属性时, 则必须使用"super."的声明,显式的调用父类被重写的方法或父类中声明的同名的属性。
4.5 this 和 super
this:当前对象
- 在构造器和非静态代码块中,表示正在 new 的对象
- 在实例方法中,表示调用当前方法的对象
super:引用父类声明的成员
2、this 和 super 的使用格式
- this
- this.成员变量:表示当前对象的某个成员变量,而不是局部变量
- this.成员方法:表示当前对象的某个成员方法,完全可以省略 this.
- this()或 this(实参列表):调用另一个构造器协助当前对象的实例化,只能在构造器首行,只会找本类的构造器,找不到就报错
- super
- super.成员变量:表示当前对象的某个成员变量,该成员变量在父类中声明的
- super.成员方法:表示当前对象的某个成员方法,该成员方法在父类中声明的
- super()或 super(实参列表):调用父类的构造器协助当前对象的实例化,只能在构造器首行,只会找直接父类的对应构造器,找不到就报错
5. 面向对象特征之三 : 多态
如何理解多态 :
- 理解:理解为一个事物的多种形态。
生活举例:
女朋友:我想养一个宠物。 孩子:我想要一个玩具。 老板:张秘书,安排一个技术科的同事,跟我一起下周出差。
5.1 多态的形式和体现
5.1.1 对象的多态性:
多态性,是面向对象中最重要的概念,在 Java 中的体现:**对象的多态性:父类的引用指向子类的对象**
格式:(父类类型:指子类继承的父类类型,或者实现的接口类型)
代码语言:javascript复制父类类型 变量名 = 子类对象;
举例:
代码语言:javascript复制Person p = new Student();
Object o = new Person();//Object类型的变量o,指向Person类型的对象
o = new Student(); //Object类型的变量o,指向Student类型的对象
对象的多态:在 Java 中,子类的对象可以替代父类的对象使用。所以,一个引用类型变量可能指向(引用)多种不同类型的对象
5.1.2 多态的理解
Java 引用变量有两个类型:编译时类型
和运行时类型
。编译时类型由声明
该变量时使用的类型决定,运行时类型由实际赋给该变量的对象
决定。简称:编译时,看左边;运行时,看右边。
- 若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)
- 多态情况下,“看左边”:看的是父类的引用(父类中不具备子类特有的方法) ** “看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)**
多态的使用前提:① 类的继承关系 ② 方法的重写
5.2 多态的好处与弊端
弊端:
- 在多态的场景下,我们创建了子类的对象,也加载了子类特有的属性和方法。但是由于声明为父类的引用, 导致我们没有办法直接调用子类特有的属性和方法。
好处:
- 极大的减少了代码的冗余,不需要定义多个重载的方法。
举例:
代码语言:javascript复制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();
}
}
5.3 向上转型和向下转型
首先,一个对象在 new 的时候创建是哪个类型的对象,它从头至尾都不会变。即这个对象的运行时类型,本质的类型用于不会变。但是,把这个对象赋值给不同类型的变量时,这些变量的编译时类型却不同。
为什么要类型转换:
因为多态,就一定会有把子类对象赋值给父类变量的时候,这个时候,在编译期间
,就会出现类型转换的现象。
但是,使用父类变量接收了子类对象之后,我们就不能调用
子类拥有,而父类没有的方法了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做类型转换,使得编译通过
。
- 向上转型:当左边的变量的类型(父类) > 右边对象/变量的类型(子类),我们就称为向上转型
- 此时,编译时按照左边变量的类型处理,就只能调用父类中有的变量和方法,不能调用子类特有的变量和方法了
- 但是,运行时,仍然是对象本身的类型,所以执行的方法是子类重写的方法体。
- 此时,一定是安全的,而且也是自动完成的
- 向下转型:当左边的变量的类型(子类)<右边对象/变量的编译时类型(父类),我们就称为向下转型
- 此时,编译时按照左边变量的类型处理,就可以调用子类特有的变量和方法了
- 但是,运行时,仍然是对象本身的类型
- 不是所有通过编译的向下转型都是正确的,可能会发生 ClassCastException,为了安全,可以通过 isInstanceof 关键字进行判断
5.4 如何向上或向下转型
向上转型:自动完成
向下转型:(子类类型)父类变量
代码语言:javascript复制package com.atguigu.polymorphism.grammar;
public class ClassCastTest {
public static void main(String[] args) {
//没有类型转换
Dog dog = new Dog();//dog的编译时类型和运行时类型都是Dog
//向上转型
Pet pet = new Dog();//pet的编译时类型是Pet,运行时类型是Dog
pet.setNickname("小白");
pet.eat();//可以调用父类Pet有声明的方法eat,但执行的是子类重写的eat方法体
// pet.watchHouse();//不能调用父类没有的方法watchHouse
// 向下转型
Dog d = (Dog) pet;
System.out.println("d.nickname = " d.getNickname());
d.eat();//可以调用eat方法
d.watchHouse();//可以调用子类扩展的方法watchHouse
Cat c = (Cat) pet;//编译通过,因为从语法检查来说,pet的编译时类型是Pet,Cat是Pet的子类,所以向下转型语法正确
//这句代码运行报错ClassCastException,因为pet变量的运行时类型是Dog,Dog和Cat之间是没有继承关系的
}
}
5.5 instanceof 关键字
instanceof 的使用:
- 建议在向下转型之前,使用 instanceof 进行判断,避免出现类型转换异常
- 格式: a instanceOf A : 判断对象 a 是否是类 A 的实例。
- 如果 a instanceOf A 返回 true,则: a instanceOf superA 返回也是 true。其中,A 是 superA 的子类。
为了避免 ClassCastException 的发生,Java 提供了instanceof
关键字,给引用变量做类型的校验。如下代码格式:
//检验对象a是否是数据类型A的对象,返回值为boolean型
对象a instanceof 数据类型A
- 说明:
- 只要用 instanceof 判断返回 true 的,那么强转为该类型就一定是安全的,不会报 ClassCastException 异常。
- 如果对象 a 属于类 A 的子类 B,a instanceof A 值也为 true。
- 要求对象 a 所属的类与类 A 必须是子类和父类的关系,否则编译错误。
代码:
代码语言:javascript复制package com.atguigu.polymorphism.grammar;
public class TestInstanceof {
public static void main(String[] args) {
Pet[] pets = new Pet[2];
pets[0] = new Dog();//多态引用
pets[0].setNickname("小白");
pets[1] = new Cat();//多态引用
pets[1].setNickname("雪球");
for (int i = 0; i < pets.length; i ) {
pets[i].eat();
if(pets[i] instanceof Dog){
Dog dog = (Dog) pets[i];
dog.watchHouse();
}else if(pets[i] instanceof Cat){
Cat cat = (Cat) pets[i];
cat.catchMouse();
}
}
}
}
6. Object 类的使用
类java.lang.Object
是类层次结构的根类,即所有其它类的父类。每个类都使用 Object
作为超类。
7. 面试题
==和 equals 的区别:
- == 既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型就是比较内存地址
- equals 的话,它是属于 java.lang.Object 类里面的方法,如果该方法没有被重写过默认也是==;我们可以看到 String 等类的 equals 方法是被重写过的,而且 String 类在日常开发中用的比较多,久而久之,形成了 equals 是比较值的错误观点。
- 具体要看自定义类里有没有重写 Object 的 equals 方法来判断。
- 通常情况下,重写 equals 方法,会比较类中的相应属性是否都相等。