5.面向对象编程(中)
5.1OOP特征二:继承性
5.1.1继承性的好处
- 减少了代码的冗余
- 提高了代码的复用性便于功能的扩展
- 为之后多态性的使用,提供了前提
5.1.2继承性的格式
class A extends B{}
A:子类、派生类、subclass
B:父类、超类、基类、superclass
5.1.3说明
- 体现:一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有属性和方法
- 特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。只有因为封装性的影响,使得子类不能直接调用父类的结构而已。
- 子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。子类和父类的关系,不同于子集和集合的关系。
5.1.4继承性的规定
- 一个类可以被多个子类继承
- 单继承性:一个类只能有一个父类
- 多层继承:A→B→C→D
- B为A的直接父类,C、D为A的间接父类
- 子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法
- 所有的java类(除java.lang.object类之外)都直接或间接的继承于java.lang.0bject类
- 意味着,所有的java类具有java.lang.ob Tect类声明的功能。
5.2方法的重写(override)
5.2.1定义
在子类中可以根据需要对从父类中继承来的方法进行改造,也称为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。
5.2.2要求
1.子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表
2.子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型
3.子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限
- 子类不能重写父类中声明为private权限的方法
4.子类方法抛出的异常不能大于父类被重写方法的异常
5.2.3注意
子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的方法。
5.2.4应用
重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。
5.2.5面试题
区分方法的重载与重写
5.3四种访问权限修饰符
同第四章
5.4关键字:super
super理解为:父类的
super可以用来调用:属性、方法、构造器
5.4.1super的使用
5.4.1.1super调用属性和方法
- 1我们可以在子类的方法或构造器中。通过使用"super.属性“或"super.方法"的方式,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略"super."
- 当子类和父类中定义了同名的属性时
- 我们要想在子类中调用父类中声明的属性,则必须显式的使用"super.属性"的方式,表明调用的是父类中声明的属性;
- 若想要在子类中调用他自己的属性则需要使用“this.属性”或“属性”的方式。
- 当子类和父类中重写了同名的方法时,同上述属性调用方式
- 当子类没有定义与父类同名的属性或重写父类方法时,”super.属性/方法“、“this.属性/方法“或直接使用”属性/方法“三者效果相同,但调用过程不同。第一种会先从其父类中找该属性/方法,找不到再去子类自身去找;第二种和第三种会先从子类自身找该属性/方法,找不到再去父类中找。
- 以上涉及到的父类包括直接父类和间接父类
5.4.1.2super调用构造器
我们可以在子类的构造器中显式的使用"super(形参列表)"的方式
调用父类中声明的指定的构造器"super(形参列表)"的使用,必须声明在子类构造器的首行!
我们在类的构造器中,针对于"this(形参列表)"或"super(形参列表)"只能二选一,不能同时出现
在构造器的首行没有显式的声明"this(形参列表)“或"super(形参列表)”,则默认调用的是父类中空参的构造器”super()“
5.5子类对象实例化过程
5.5.1.从结果上来看
子类继承父类以后,就获取了父类中声明的属性或方法。
创建子类的对象,在堆空间中,就会加载所有父类中声明的属性
5.5.2从过程上来看
当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,直到调用了java.lang.object类中空参的构造器为止。正因为加载过所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用。
明确:虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。
5.6OOP特征三:多态性
5.6.1.理解多态性
可以理解为一个事物的多种形态。
5.6.2.何为多态性
-
对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)
-
//其中Man类为Person类的子类 Person p=new Man();
-
5.6.3.多态的使用
-
当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法—虚拟方法调用,但是ctrl+单击该方法时,跳转到父类的该方法。
-
//在Man类中重写了Person父类的eat和walk方法 p.eat();//调用Man类中重写之后的eat p.walk();//调用Man类中重写之后的walk p.earnMoney();//调用Man类中特有方法报错
-
-
有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。
-
总结:编译,看左边;运行,看右边。
5.6.4.多态性的使用前提
- 类的继承关系
- 方法的重写
5.6.5属性无多态
对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)
-
//其中Man类为Person类的子类 Person p=new Man(); //age为Man类和Person类中同名属性 System.out.print(p.age);//输出Person中的age属性
5.6.6虚拟方法调用
5.6.7小结:方法的重载与重写
1.二者的定义细节:略
2.从编译和运行的角度看:
重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。
3.所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定”;
而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”。
5.7每日一考
2.只有一个;多个;可以;可以(证明:让子类调用父类私有属性的get/set方法,就可以访问父类私有属性;在父类中写一个公有方法,在这个方法里调用其私有方法,其子类可以通过父类这个公有访问到父类的私有方法)
面试题:
多态是编译时行为还是运行时行为?
答:运行时行为
因为子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
5.6.8向下转型
//Man类为Person类的子类
Person p2= new Man();
//name为Man和Person都有的属性
p2.name = "Tom" ;
//earnMoney和isSmoking为Man类特有的方法
//p2不能调用子类所特有的方法、属性,因为编译时,p2是Person类型
p2.earnMoney();//报错,编译不通过
p2.isSmoking = true;//报错,编译不通过
//有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,
//导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。
//如何才能调用子类特有的属性和方法?
//向下转型:使用强制类型转换符。
Man m1 =(Man)p2;
m1.earnMoney();//不报错
m1.isSmoking = true;//不报错
//向下转型的风险:转换失败,抛出ClassCastExceptiong异常
//Woman也是Person类的子类,其特有的属性为goShopping
Women w1=(Woman)p2;//转换失败
w1.goShopping;//p2实际上是含有Man类特有属性和方法的Person类型变量,所以不含Women的特有属性和变量
//练习:
//问题一:编译时通过,运行时不通过
//举例一:
Person p3 =new woman();
Man m3 = (Man)p3;
//举例二:
Person p4 = new Person();
Man m4 = (Man)p4;
//问题二:编译通过,运行时也通过
object obji = new woman();
Person p = (Person)obi;
//问题三:编译不通过
Man m5 = new woman();
5.6.9instanceof操作符
使用
a instanceof A:判断对象a是否是类A的实例。如果是,返回true;如果不是,返回false。
使用情境
为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型。如果返回false,不进行向下转型。
//接5.6.8中代码
//在编译时p2为Person类型,该类型可以使用instanceof操作符,所以编译通过
//在运行时p2会被识别出来时Man类型,所以该判断语句为false
if(p2 instanceof Woman){
Woman w1 = (Woman)p2;
w1.goShopping();
System.out.print1n("******woman******");
}
//在编译时p2为Person类型,该类型可以使用instanceof操作符,所以编译通过
//在运行时p2会被识别出来时Man类型,所以该判断语句为true
if(p2 instanceof Man){
Man m2 =(Man)p2;
m2.earnMoney();
System.out.print1n( "******Man******");
}
5.8Object类
5.8.1说明
- 1.0bject类是所有Java类的根父类
- 2.如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.object类
- 3.object类中的功能(属性、方法)就具有通用性。
属性:无
方法:equals() / toString() / getclass( ) /hashcode() / clone()/finalize()/wait() 、notify()、notifyAll() - 4.object类只声明了一个空参的构造器
5.8.2面试题
5.8.2.1.final、finally、finalize的区别?
5.8.2.2.==和equals()的区别
== 是运算符
-
1.可以使用在基本数据类型变量和引用数据类型变量中
-
2.如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型要相同)
如果比较的是引用数据类型变量:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体 -
int i = 10; int j = 10; double d = 10.0; System.out.println(i ==j);//true System.out.print1n(i == d);//true,因为==为运算符,运算符两侧会有自动类型转换 boolean b = true; System.out.print1n(i == b);//报错,因为boolean不能使用运算符 char c = 10;//此时c为ascll=10的字符 System.out.println(i == c);//true char c1 = 'A'; char c2 = 65; system.out.println(c1 == c2); //true,自动类型转换char→int,‘A’的ascll为65 String str1 =new String("atguigu"); String str2 = new String("atguigu"); system.out.print1n(str1 == str2); //false,地址值比较。str1和str2分别指向堆中不同的对象
equals是方法而非运算符
-
1.是一个方法,而非运算符
-
2.只能适用于引用数据类型
-
3.Object类中equals()的定义:
public boolean equals (object obi) {
return (this == obi);
}
说明: Object类中定义的equals()和==的作用是相同的:比较两个对象的地址值是否相同.即两个引用是否指同一个对象 -
4.像String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的"实体内容是否相同。
-
//引用类型 //Customer是自定义的类(没有重写父类Object的eqals方法) Customer cust1 =new customer( "Tom",21); Customer cust2 = new customer("Tom",21); System.out.println( cust1 == cust2);//false String str1 = new String( "atguigu"); String str2 =new String( "atguigu"); System.out.println(str1 == str2);//false System.out.print1n("****************************"); System.out.println(cust1.equals(cust2));//false System.out.print1n(str1.equals(str2));//true Date date1 =new Date(32432525324L); Date date2 = new Date(32432525324L); system.out.println( date1.equals(date2)); //true
-
5.通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的"实体内容"是否相同。那么,我们就需要对Object类中的equals()进行手动或自动重写,自动重写使用eclipse的generate功能
5.8.3重写equals()方法的原则
- 对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。
- 自反性: x.equals(x)必须返回是“true”。
- 传递性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。
- 一致性:如果x.equals(y)返回是“true”,只要x和ly内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。
- 任何情况下,x.equals(null),永远返回是“false”;x.equals(和lx不同类型的对象)永远返回是“false”。
5.8.3toString()方法
1.当我们输出一个对象的引用时,实际上就是调用当前对象的toString()
2.Object类中toString()的定义:
public String toString(){
return getClass().getName() +“@”+ Integer.toHexString(hashCode());
}
3.像String、Date、File、包装类等都重写了Object类中的toString()方法。
使得在调用对象的toString()时,返回"实体内容"信息
4.自定义类也可以手动/自动重写toString()方法,当调用此方法时,返回对象的"实体内容",使用Eclipse的generate功能可以自动生成toString的重写
5.9包装类的使用
5.9.1定义
5.9.2基本类型、包装类和String类之间的转换
5.9.3面试题
第一个输出结果为true,第二个输出结果为false,
Integer内部定义了IntegerCache静态结构,IntegerCache中定义了静态数组Integer[],保存了从-128127范围的整数。如果我们使用自动装箱的方式,给Integer赋值的范围在’-128127范围内时,可以直接使用数组中的元素,不用再去new了。
目的:提高效率
因此i和j并没有去new新的Integer的对象,而是指向Integer的静态结构IntegerCache中静态数组Integer[]的同一个地址,该地址存的是1
5.10Java中Junit单元测试
步骤
- 1.选中当前工程→右键→build path→add libraries→JUnit→下一步→下一步
- 2.创建Java类,进行单元测试
要求:此类是public的②此类提供公共的无参的构造器 - 3.此类中声明单元测试方法。
要求:方法的权限是public,没有返回值,没有形参 - 4.此单元测试方法上需要声明注解:@Test,并在单元测试类中导入:import org.junit.Test;
- 5.声明好单元测试方法以后,就可以在方法体内测试相关的代码。
- 6.写完代码以后,左键双击单元测试方法名,右键:run as→JUnit Test
说明:
- 1.如果执行结果没有任何异常:绿条
- 2.如果执行结果出现异常:红条