一、this关键字的使用
问题背景:在声明一个属性对应的setXXX方法时,通过形参给对应属性赋值,如果形参名和属性名相同,在方法内该如何区分这两个变量呢?
解决方案:使用this。即用this来区分成员变量
和局部变量。
同样在构造方法里也可以使用this对属性赋值。
按住ctrl,鼠标放在对应单词上,可进行定位。
1、this可调用的结构
方法、属性(成员变量)、构造器。
-
它在方法(准确的说是实例方法或非static的方法)内部使用,表示调用该方法的对象
-
它在构造器内部使用,表示该构造器正在初始化的对象。
2、 this调用构造器
- 格式:this(形参列表)或者this()
- 可以在类的构造器中,调用当前类中指定的其它的构造器。
- 要求:this(形参列表)必须声明在当前构造器的首行。
- this(形参列表)在构造器中最多声明一个。
- 如果一个类中声明了n个构造器,则最多有n-1个构造器可以声明this(形参列表)结构。
二、面向对象的特征二:继承性
1、继承的理解
- 自上而下:定义了一个类A,在定义另一个类B时,发现与类A功能属性相似,则考虑类B继承A。
- 自下而上:定义了类B,C,D,发现这三个类中中有相同的属性和方法,则可以考虑将相同的属性和方法提取,封装到类A中。让B、C、D继承类A,同时,B、C、D中相同的方法就可以删除了。
判断能不能继承,可以通过is-a的关系来决定。例如,在Animal类下,可以有cat类继承Animal,因为cat is an animal。不能为了继承而继承,虽然student类中也有像cat类中的name、age等相同的属性,但简单理解,student is not a cat。所以student类不能继承cat类。
2、继承的好处
-
继承的出现减少了代码冗余,提高了代码的复用性。
-
继承的出现,更有利于功能的扩展。
-
继承的出现让类与类之间产生了
is-a
的关系,为多态的使用提供了前提-
继承描述事物之间的所属关系,这种关系是:
is-a
的关系。可见,父类更通用、更一般,子类更具体。
-
3、继承的说明
- 有了继承性以后,子类可以获取到父类所有的属性和方法。
但由于父类封装性的存在,即private String name了。子类则不能直接使用name(虽不能使用,但子类确实是获取到这个name了),此时父类需要提供get、set方法,子类通过get()方法得到name值。
- 获取某个实例对象p1所属类的父类,语句是:p1.getClass().getSuperclass()。
- java中没有显式说明父类时,默认的父类是java.lang.Object。
- 一个父类可以由多个子类继承,一个子类不可以继承多个父类。
三、方法的重写
重写的规则
- 父类被重写的方法和子类重写的方法的方法名和形参列表一样。 (这样才能确定子类覆盖的父类哪一个方法。)
- 子类重写的方法的修饰符要>=父类被重写的方法的修饰符。但子类中不能重写父类中声明为private的方法。
- 关于返回值类型 :
1、父类被重写的方法的返回值类型是void,子类重写的方法返回值类型也必须是void。
2、 父类被重写的方法的返回值类型是基本数据类型,子类重写的方法返回值类型和父类一样。
3、父类被重写的方法的返回值类型是引用数据类型,子类重写的方法返回值类型可以和父类一样 或者是 父类的子类。
@OverRide 注解起到的作用:当你的方法名和父类中被重写的那个方法名并不一样时,会提示错误。
Q:当父类某个方法A被子类重写了,但子类中如果还想用这个方法A,此时已经被子类重写覆盖了,但可能还要用到父类的这个没有被重写的方法A,该怎么办呢?
答:此时就可以用super关键字,super.A()。
四、super关键字
1、使用super的原因
- 第一个就是上一节的那个Q。
- 第二个就是,如果子类和父类的属性名相同了,那如果要使用父类的这个属性,那就需要使用super.属性名。
2、super的调用
super可以调用属性(super.属性)、方法(super.方法)、构造器(单独一节说明)。 调用也要不影响封装性为前提。
3、super调用构造器
- 子类继承父类时,不会继承父类的构造器。只能通过“super(形参列表)”的方式调用父类指定的构造器。
例如,在子类Student类中写一个构造器Student( ),其中要写上super()来调用父类Person的空参构造器。若调用父类有参构造器,则使用super(参数列表)。
- 规定:“super(形参列表)”,必须声明在子类构造器的首行。
- 前面讲过,在构造器的首行可以使用"this(形参列表)",调用本类中重载的构造器, 结合②,结论:在构造器的首行,"this(形参列表)" 和 "super(形参列表)"只能二选一。
- 如果在子类构造器的首行既没有显示调用"this(形参列表)",也没有显式调用"super(形参列表)", 则子类此构造器默认调用"super()",即调用父类中空参的构造器。
- 由前面两条得到结论:子类的任何一个构造器中,要么会调用本类中重载的构造器,要么会调用父类的构造器。 只能是这两种情况之一。
- 由上一条得到:一个类中声明有n个构造器,最多有n-1个构造器中使用了"this(形参列表)",则剩下的那个一定使用"super(形参列表)"。
在通过子类的构造器创建对象的过程中,一定会直接或间接的调用到父类的构造器。(但调用构造器的目的不是为了创建对象) 。
正是因为调用父类构造器,父类中的属性或方法才能加载到内存中,供子类对象使用。即子类中我们可以调用父类中的属性,假设子类对象实例s1,它调用父类数性name,s1.name,之所以能调用,,是因为在创建子类对象s1后,在调用子类构造器时,也调用到了父类的构造器(因为即使没有在子类构造器中显式声明,也会默认调用父类无参构造器),所以父类中的属性、方法等就能加载到内存中,进而s1才能调用父类的name。
五、子类对象实例化全过程
当我们通过子类的构造器创建对象时,子类的构造器一定会直接或间接的调用到其父类的构造器,而其父类的构造器同样会直接或间接的调用到其父类的父类的构造器,…….,直到调用了0bject类中的构造器为止。
当创建子类的对象时,即Dog dog = new Dog("小花","小红");,下面是内存图:
所以说,当我们创建子类对象后,子类对象会获取到父类的所有方法和属性。 因为都加载到内存里了。此时,内存中也是只有一个对象!
六、面向对象的特征三:多态性
广义上的理解:子类对象的多态性、方法的重写;方法的重载
狭义上的理解:子类对象的多态性。
1、对象的多态性
在Java中的体现:对象的多态性:父类的引用指向子类的对象 。
格式:父类类型 变量名=子类对象。
Person p1=new Man(): //Man是Person的子类。
Person p2=new Women(): //Women也是Person的子类。
即,对象的多态:在Java中,子类的对象可以替代父类的对象使用。所以,一个引用类型变量可能指向(引用)多种不同类型的对象。
2、多态的使用前提
- 要有类的继承关系
- 要有方法的重写。
3、多态的理解
Java引用变量有两个类型:
编译时类型
和运行时类型
。编译时类型由声明
该变量时使用的类型决定,运行时类型由实际赋给该变量的对象
决定。简称:编译时,看左边;运行时,看右边。
- 若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism) 。
- 多态情况下,“看左边”:看的是父类的引用(父类中不具备子类特有的方法) “看右边”:看的是子类的对象(实际运行的是子类重写父类的方法) 。
- 多态适用于方法,不适用于属性。也就是说如果父类和子类都声明了属性name,那么p1.name的结果是父类里面值。
举例:
public class Pet {
private String nickname; //昵称
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public void eat(){
System.out.println(nickname + "吃东西");
}
}
public class Cat extends Pet {
//子类重写父类的方法
@Override
public void eat() {
System.out.println("猫咪" + getNickname() + "吃鱼仔");
}
//子类扩展的方法
public void catchMouse() {
System.out.println("抓老鼠");
}
}
public class Dog extends Pet {
//子类重写父类的方法
@Override
public void eat() {
System.out.println("狗子" + getNickname() + "啃骨头");
}
//子类扩展的方法
public void watchHouse() {
System.out.println("看家");
}
}
一、方法内局部变量的赋值体现多态
public class TestPet {
public static void main(String[] args) {
//多态引用
Pet pet = new Dog();
pet.setNickname("小白");
//多态的表现形式
/*
编译时看父类:只能调用父类声明的方法,不能调用子类扩展的方法;
运行时,看“子类”,如果子类重写了方法,一定是执行子类重写的方法体;
*/
pet.eat();//运行时执行子类Dog重写的方法
// pet.watchHouse();//不能调用Dog子类扩展的方法
pet = new Cat();
pet.setNickname("雪球");
pet.eat();//运行时执行子类Cat重写的方法
}
}
二、方法的形参声明体现多态
意思就是,方法的形参是父类类型, 实参可以是子类对象。
三、方法返回值类型体现多态
返回值类型是父类类型,实际返回的是子类对象 。
4、多态的好处和弊端
好处: 减少代码冗余,不需要设置过多重载的方法。
public class Dog {
public void eat(){
System.out.println("狗啃骨头");
}
}
public class Cat {
public void eat(){
System.out.println("猫吃鱼仔");
}
}
public class Person {
private Dog dog;
//adopt:领养
public void adopt(Dog dog){
dog.eat();
}
//feed:喂食
public void feed(){
if(dog != null){
dog.eat();
}
}
/*
问题:
1、从养狗切换到养猫怎么办?
修改代码把Dog修改为养猫?
2、或者有的人养狗,有的人养猫怎么办?
3、要是还有更多其他宠物类型怎么办?
如果Java不支持多态,那么上面的问题将会非常麻烦,代码维护起来很难,扩展性很差。
*/
}
因为多态性质的存在,代码可以修改为:
public class Person {
private Dog dog;
//adopt:领养
public void adopt(Animal animal){//Animal是Dog、Cat的父类,这样不需要重载过多的adopt(形参)方法,就能实现对不同动物的adopt.
animal.eat();
}
//feed:喂食
public void feed(){
if(dog != null){
dog.eat();
}
}
}
public class PersonTest{
public static void main(String[] args){
Person p=new Person();
p.adopt(new Dog()); //此时实参可以是子类对象。
p.adopt(new Cat());
}
}
弊端:
Q:Person p1=new Man();针对于创建的对象p1,在内存中是否加载了Man()类中特有的属性和方法呢?
加载了。问题在于,虽然加载了,但是并不能直接调用Man中特有的属性和方法!
那就不如直接Man m=new Man();这样既可以调用父类的也可以调用子类特有的。所以这里的多态性只针对于下面标注的这一块,作为形参。
那其实还是能调用的,这就需要使用接下来介绍的方法!!!——向下转型
5、向下转型和向上转型
向上转型:自动完成。
向下转型:(子类类型)父类变量。
可能会出现的问题:发生ClassCastException ,类型转换异常。
Person p1=new Woman();//此时是Women对象
Man m1=(Man)p1;//此时编译器并不会报错,但运行会出现ClassCastException问题。
6、instanceof关键字
在向下转型之前,使用instanceof
关键字判断类型,避免ClassCastException的发生。
//检验对象a是否是数据类型A的对象,返回值为boolean型
格式:if(对象a instanceof 数据类型A ){Man m1=(Man)p1;
}
只要用instanceof判断返回true的,那么强转为该类型就一定是安全的,不会报ClassCastException异常。
开发过程中,遇到需要调用子类对象特有的方法或属性了,就用向下转型,只不过,要先用instanceof判断一下!!格式如上。
7、虚方法的调用
在Java中虚方法是指在编译阶段不能确定方法的调用入口地址,在运行阶段才能确定的方法,即可能被重写的方法。
七、equals()的使用
Object类中的方法。适用于任何引用数据类型。
1、使用说明
(一)自定义的类在没有重写0bject中equals()方法的情况下,调用的就是0bject类中声明的equals(),比较两个对象的引用地址是否相同。(或比较两个对象是否指向了堆空间中的同一个对象实体) 。
(二)对于像String、File、Date和包装类等,它们都重写了0bject类中的equals()方法,用于比较两个对象的实体内容是否相等。
2、开发中的使用说明
实际开发中,针对于自定义的类,常常会判断两个对象是否equals(),而此时主要是判断两个对象的属性值是否相等。所以:我们要重写0bject类的equals()方法。
重写方法:手动编写;调用IDEA自动实现。自动实现步骤:
1、输入equals+回车,会跳出如下框:
或者先Alt+Insert,选择equals()和hashCode(),也会跳出如上框。
2、点击next
这一步选择是判断两个字段还是选择判断一个字段相等。后面一路next。
非空看情况。
3、面试题:==和equals的区别
==使用范围:基本数据类型(判断值是否相等)、引用数据类型(比较两个引用变量的地址值是否相等)。
equals使用范围:引用数据类型(具体使用,可谈重写equals和不重写equals的点。见2、)
八、toString()的使用说明
自定义的类,在没有重写0bject类的tostring()的情况下,默认返回的是当前对象的地址值。
像string、File、Date或包装类等0bject的子类,它们都重写了0bject类的toString(),在调用tostring()时返回当前对象的实体内容。
在实际开发中,自定义的类一般也要重写Object类中的toString方法。
Idea可自动实现重写,步骤和equals()类似。Alt+Insert。。。