目录
5.1 面向对象特征之二:继承性
1 为什么使用继承
![](https://i-blog.csdnimg.cn/blog_migrate/69ef30784d54289072bad1bf9b03234b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/3af32dd2fc85561b8b38cfb63f3e5fe5.png)
继承的好处:
-
继承的出现减少了代码冗余,提高了代码的复用性。
-
继承的出现,更有利于功能的扩展。
-
继承的出现让类与类之间产生了关系,提供了多态的前提。
2 继承的格式
class A extends B{}
A: 子类、派生类、subclass
B: 父类、超类、基类、superclass
体现:一单子类继承父类后,子类A中就获取了父类B中声明的结构:属性、方法。(私有属性也可以继承,但由于封装的影响,不能直接使用,需要用get set方法。)
![](https://i-blog.csdnimg.cn/blog_migrate/d66a6884149df6898643bf62c1babcea.png)
子类继承父类后,可以声明自己的属性与方法,实现功能的扩展。
3 java中关于继承的规定
-
一个类可以被多个子类继承。
-
java中类的单继承性:一个类只能有一个父类。(c++可以支持多继承)
-
java中类的多层继承:子父类是相对概念。
-
子类直接继承的父类称为直接父类,间接继承的父类称为直接父类。子类继承父类后,获取了直接父类和所有间接父类中声明的属性与方法。
![](https://i-blog.csdnimg.cn/blog_migrate/38baca93e597e293eb0238eb9b63c575.png)
4 Object
-
如果我们没有现实的声明一个父类的话,则此类继承于 java.lang.Object类。
-
所有的java类(除java.lang.Object外)都直接或间接继承于object类。
补充:eclipse的debug使用
-
设置断点(可以设置多个)双击左侧设置
-
debug as java application
-
常用操作(菜单栏中)
操作
|
作用
|
step into 跳入
|
进入当前行所调用的方法中
|
step over跳过
|
执行完当前行语句,进入下一行
|
step return跳回
|
执行完当前行所在方法,进入下一行
|
drop to frame
|
回到当前行所在方法的第一行
|
resume恢复
|
执行完当前行所在断点所有代码,进入下一个断点,如果没有就结束
|
5.2 方法的重写
1 定义
在子类中可以根据需要对从父类中继承来的方法进行改造,也称
为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。
重写以后,当创建子类对象以后,通过子类对象调用子父类中同名同参数的方法时,实际执行的是子类重写父类的方法。
2 要求
-
子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表
-
子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型
-
父类为void,子类只能是void
-
父类返回值类型为A,子类返回值类型只能是A或A的子类。
-
父类返回值类型为基本数据类型,子类返回值必须为相同的类型
//父类
public class Father{
public Object info(){
}
}
//子类
public class Son extends Father{
public String info(){
//String为Object的子类,所以属于重写
}
}
-
子类重写的方法使用的访问权限 不能小于父类被重写的方法的访问权限( 子类不能重写父类中声明为private权限的方法)
-
子类方法抛出的异常不能大于父类被重写方法的异常
注意:子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为
static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的static方法。
5.3 四种访问权限修饰符
![](https://i-blog.csdnimg.cn/blog_migrate/268517e672257b48a52885fb3200a028.png)
5.4 super关键字
1 使用
super关键字可以理解为:父类的
可以用来修饰属性、方法、构造器
修饰属性:
当子类定义的属性和父类定义的属性重名时,默认调用(或用this调用)的是子类的属性,用super调用父类的属性。
不冲突时,this和super调用的是一样的。
修饰方法:
子类中想要调用父类被重写的方法时,必须显式使用super.method();
修饰构造器:
-
使用super(形参列表); 可以调用父类指定的构造器。
-
super(形参列表); 必须声明在子类构造器的首行。
-
this(参 数列表)或者super(参数列表) 只能”二选一”。当既没有显示声明this(参 数列表) ,也没有声明 super(参数列表) ,默认调用super()
-
在类的多个构造器中,至少有一个类的构造器中使用了super(形参列表),调用父类的构造器。
public class Father{
int id = 1;
public void method(){
sysout("父类");
}
}
public class Son extends Father{
int id = 2;
public void method(){
sysout("子类");
}
public void show(){
sysout(id); // 1
sysout(super.id); // 2
method(); //子类
super.method(); //父类
}
}
比较this和super:
![](https://i-blog.csdnimg.cn/blog_migrate/a5ca90f6aa18f54db6c56f55717a1d44.png)
5.5 子类对象实例化过程
![](https://i-blog.csdnimg.cn/blog_migrate/f866b3fb5de794b256a88ae110be15c5.png)
1 从结果来看
子类继承父类后,获取了父类的属性和方法。
创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。
2 从过程来看
当我们通过子类的构造器创建对象时,一定会直接或间接调用父类的构造器,进而调用父类的父类的构造器......。直到调用了java.lang.Object中的空参构造器为止。正因为加载过所有父类的结构,所以才可以看到内存中有父类的结构,子类才可以调用。
(注:这个过程自始至终只创建了子类一个对象)
5.6 面向对象特征之三:多态性
1 理解多态性
可以理解为一个事物的多种形态
对象的多态性:
父类的引用指向子类的对象(子类的对象赋给父类的引用)
虚拟方法调用:当调用子父类同名同参的方法时,实际执行的是子类重写父类的方法。
(子类中定义了与父类同名同参数的方法,在多态情况下,
将此时父类的方法称为虚拟方法,父
类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法
确定的。
)
多态性是一种运行时行为。
多态作用:
提高了代码的通用性,常称作接口重用
public class Father{
public void method(){
sysout("父类");
}
}
public class Son extends Father{
public void method(){
sysout("子类");
}
public void sonMethod(){
sysout("子类特有的方法");
}
}
//对象的多态性
Father f = new Son();
//虚拟方法调用
f.method(); //输出:子类
f.sonMethod(); //会报错
2 多态的使用:虚拟方法调用
有了多态性以后,编译期只能调用父类中声明的方法,但在运行期,实际执行的是子类使用父类的方法。
编译看左边,执行看右边
使用前提:
-
类的继承关系
-
方法的重写
使用举例:
public class Test {
public static void main(String[] args) {
Test t = new Test();
t.func(new Dog());
t.func(new Cat()); //如果没有多态性,还需要多些两个cat和dog的func重载方法
}
public void func(Animal animal) { //Animal animal = new Animal();
animal.shout();
}
}
class Animal{
public void shout() {
System.out.println("叫");
}
}
class Dog extends Animal{
public void shout() {
System.out.println("汪汪汪");
}
}
class Cat extends Animal{
public void shout() {
System.out.println("喵喵喵");
}
}
注:对象的多态性只适用于方法,不适用于属性。(编译运行都看左边,也就是只会调用父类的属性)
动态绑定:编译时类型为父类型,而方法的调用是在运行时确定的,运行时类型为子类型
。
![](https://i-blog.csdnimg.cn/blog_migrate/20bf70d2a5e06008c14633ba3c25a020.png)
方法重载与重写:
1.定义:
2.从编译和运行的角度:
重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不
同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了
不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类
和子类的,即子类可以重载父类的同名不同参数的方法。
所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,
这称为
“
早绑定”或“静态绑定”
;
而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体
方法,这称为
“
晚绑定”或“动态绑定”
。(重写可以理解为多态)
多态练习:
![](https://i-blog.csdnimg.cn/blog_migrate/043420df0f2b94aca61e33da39f4574e.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c09d13b31157820b7a2239060092b22b.png)
注:
-
若子类重写了父类的方法,就意味着子类定义的方法彻底覆盖了父类里的同名方法,系统不可能把父类里的方法转移到子类中。(编译看左边,运行看右边)
-
对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量。(编译运行都看左边)
3 instance操作符
有了对象的多态性以后,内存中实际加载了子类特有的属性和方法。由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类的属性方法不能调用。
如何调用子类方法属性?
向下转型:使用强制转换符。
Father f = new Son();
Son s = (Son)f;
![](https://i-blog.csdnimg.cn/blog_migrate/b2ebdc37233304ac63030e016080581d.png)
注:使用强转时,可能出现ClassCastException的异常。
Animal a = new Dog();
Cat c = (Cat)a; //报ClassCastException错
解决:使用instanceof关键字
instanceof关键字使用:向下转型前先进性判断,如果返回true,则继续执行,否则不执行。
// a instanceof A : 判断对象a是否是A类的实例。如果是,返回true;如果不是,返回false。
if (c instanceof Cat){ //如果没有异常,则执行余下语句
//执行Cat的方法
System.out.println("cat");
}
注:如果a instanceof A返回为true,则a instanceof B返回也为true,其中B是A的父类。
5.7 Object类的使用
1 介绍
Object类是所有java类的根父类
如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类
2 Object类中的属性和方法
属性:无
构造器:只声明了空参构造器
方法:equals() / toString() / getClass() / hashCode() / clone() / finalize() / wait() / notify() / notifyAll()
3 equals与==
==:运算符
-
可以使用在基本数据类型和引用数据类型变量中
-
如果比较的是基本数据类型变量,比较的是 变量保存的数据是否相等;如果比较的是引用数据类型变量,比较的是两个对象的 地址值是否相同。
int i = 10;
char c = 10;
boolean b = true;
int i2 = 65;
char c1 = 'A';
char c2 = 65;
String str1 = new String("abc");
String str2 = new String("abc");
String str3 = "abc";
String str4 = "abc";
System.out.println(i == c); //true
System.out.println(i2 == c1); //true
System.out.println(c2 == c1); //true
System.out.println(b == i); //报错
System.out.println(str1 == str2); //false
System.out.println(str3 == str4); //false
equals():方法
-
只适用于引用数据类型
-
Object类中equals()的定义:与==的作用相同
public boolean equals(Object obj){
return (this == obj);
}
-
像String, Date, File, 包装类等都重写了equals()方法,比较的为两个对象“实体内容”是否相同。
-
通常情况下自定义类使用equals()类,想要比较对象的实体对象是否相同,需要对equals()重写。(一般都是自动生成)
public boolean equals(Object obj) {
if(this == obj) return true;
if(obj instanceof thisClass) {
thisClass c = (thisClass)obj;
return this.a == c.a && this.b.equals(c.b); //a:基本数据类型 b:引用数据类型
}
return false;
}
面试题:==与equals()的区别
-
== 既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型 就是比较内存地址
-
equals的话,它是属于java.lang.Object类里面的方法,如果该方法没有被重写过默认也 是==;我们可以看到String等类的equals方法是被重写过的,而且String类在日常开发中 用的比较多,久而久之,形成了equals是比较值的错误观点。
-
具体要看自定义类里有没有重写Object的equals方法来判断。
-
通常情况下,重写equals方法,会比较类中的相应属性是否都相等。
4 toString()
-
toString()方法在Object类中定义,其返回值是String类型, 返回类名和它 的引用地址。
-
当输出一个对象的引用时,实际上就是调用当前对象的toString()。
-
在进行String与其它类型数据的连接操作时,自动调用toString()方法。
Date now=new Date();
System.out.println(“now=”+now); 相当于
System.out.println(“now=”+now.toString());
-
可以根据需要在用户自定义类型中重写toString()方法。 如String 类重写了toString()方法,返回字符串的值。
s1 = "hello";
System.out.println(s1);//相当于System.out.println(s1.toString());
-
像String, Date, File, 包装类等都重写了Object类的toString()方法。
注:
char[] arr = new char[] { 'a', 'b', 'c' };
System.out.println(arr);//abc
int[] arr1 = new int[] { 1, 2, 3 };
System.out.println(arr1);//I@地址
当其余引用数据类型调用println时,调用的是println(Object obj),其中调用了toString方法,返回类名和地址值。
char[]类型调用println时,有专门的println(char x[])方法,返回值是数组内容。
补充:单元测试的使用(JUnit)
步骤:
-
选中当前工程 - 右键:bulid path - add libraries - JUnit 4 - 下一步
-
创建Java类,进行单元测试。此时java类要求:①此类是public的 ②此类提供公共的无参构造器
-
此类中声明单元测试方法。此时的方法是public的,没有返回值和形参。
-
此单元测试方法上需要声明注解:@Test,并导入import org.junit.Test; (开发中可以直接写@Test,自动处理相关步骤)
-
声明好之后就可以在方法体中测试相关代码。
-
运行:左键双击单元测试方法名,右键:run as - JUnit Test
结果:执行结果没有异常会显示绿条,否则显示红条。
5.8 包装类的使用
1 介绍
针对八种基本数据类型定义相应的引用类型—包装类(封装类)(将基本数据类型封装到类中,希望基本数据类型也有类的特性)
有了类的特点,就可以调用类中的方法,Java才是真正的面向对象
![](https://i-blog.csdnimg.cn/blog_migrate/85234eb6e8af342b96be4152b131950c.png)
2 基本数据类型,包装类和String的相互转换
![](https://i-blog.csdnimg.cn/blog_migrate/166fddeb6f0b09b102810430eb948409.png)
自动装箱与自动拆箱:jdk 5,0新特性
int i = 10;
Ingeter i1 = a; //自动装箱
int i2 = i1; //自动拆箱
练习题:
1.
public void test(){
Object o1 = true ? new Integer(1) : new Double(2.0);
sysout(o1); //1.0
}
因为编译时会将冒号前后统一成一个类型,所以做了自动类型提升,o1为double类型
2,
public void test1() {
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j);//false
Integer m = 1;
Integer n = 1;
System.out.println(m == n);//true
Integer x = 128;
Integer y = 128;
System.out.println(x == y);//false
}
Integer内部定义了IntegerCache的结构,IntegerCache中定义了Integer[],保存了-128~127范围的整数。如果使用的是自动装箱的方式定义封装类,并且赋值范围在-128~127之间时,可以直接使用数组元素,节省效率。当超出范围时,再去new。