向着Java程序员的目标前进!
day13
学习内容
面向对象—续3
继承—续
Java中的继承特点
-
Java只支持单继承,不支持多继承。
一个类只能有一个父类,不可以有多个父类。
//举例 class Zi extends Fu{}//正确 class Zi extends Fu1,Fu2...{}//错误
-
Java支持多层继承(继承体系)
//举例 class A{} class B extends A{} class C extends B{}
Java中继承的注意事项
-
子类只能继承父类中非私有的成员(成员变量或成员方法)
因为被private修饰的成员只能在本类中访问,外部类直接访问不到,只能通过公共方法间接访问。
-
子类不能继承父类的构造方法,但是可以通过super关键字去访问父类构造方法。
super关键字将在以后进行介绍
-
我们要想知道什么时候使用继承,就需要知道继承间类的关系。
继承中类之间体现的是:"is a"的关系。
"is a"关系举例:猫和动物,猫是一种动物;
水果和苹果,苹果是一种水果。
继承中成员的关系问题
这个问题可分为三个模块:
继承中成员变量的关系
成员变量访问问题
-
子类继承父类,子类的成员变量名称和父类的成员名称不一致的情况
解决办法:分别访问
-
子类继承父类,子类的成员变量名称和父类的成员名称一致的情况
解决办法:遵循“就近原则”
就近原则:
- 先在子类的局部位置中找,如果有就使用;
- 如果类的局部位置没有找到,就在子类的成员位置找,有就使用;
- 如果子类的成员位置没有找到,就在父类的成员位置找。如果有就使用,如果没有,说明当前整个继承体系就没有这个变量,系统将会报错!
-
子类继承父类,子类的成员变量名称和父类的成员名称一致,子类要访问父类的成员变量的情况
解决办法:使用super关键字
面试题练习:
/**
* 看程序,补全代码,使程序在控制台输出30,20,10
*/
class Fu3{
int num = 10 ;
}
class Zi3 extends Fu3{
int num = 20 ; //子类的成员位置
public void show(){
int num = 30 ;
//此处补全代码
//1
System.out.println() ;//局部位置
//2
System.out.println() ;
//3
System.out.println() ;
}
}
public class ExtendsTest {
public static void main(String[] args) {
Zi3 zi = new Zi3() ;
zi.show() ;
}
}
解析:
本题的子类的成员变量和父类的成员变量名称一致,而且要访问的分别是方法中的num、本类中的num、父类中的num。所以应该填num
,this.num
,super.num
。
super关键字
super的用法和this很像,但是表示的意思不一样。
- this表示本类对应的引用,super表示父类对象的地址值引用
- 访问成员变量:super.变量名表示父类的成员变量,this.变量名表示本类的成员变量
- 访问构造方法:this(…)表示访问本类的构造方法,super(…)表示访问父类的构造方法
- 访问成员方法:this.方法名()表示本类的成员方法,super.方法名()表示父类的成员方法
继承中构造方法的关系
构造方法的访问:
- 子类继承父类,不能继承父类的构造方法,但是可以通过super访问父类的构造方法
- 子类继承父类,子类的所有构造方法都会默认的访问父类的无参构造方法,其中super();可以隐藏不写,如果写了super();就要必须在第一行。
学到这里我不由提出疑问,为什么super();可以隐藏不写,一写就要写在第一行呢?
解答:因为这里的类之间是继承关系,子类会继承父类中的数据,可能还会用到父类的数据。所以,子类初始化之前,一定要完成父类数据的初始化!
面试题:继承中构造方法的关系
如果子类继承了父类,父类中没有给出无参构造方法,将会出现什么情况?如何解决?
解答:子类继承父类,子类的所有构造方法都会默认以super();访问父类的无参构造方法。如果无参构造方法都没有,子类的所有构造方法都会报错!
解决方案:
- 手动给出父类的无参构造方法
- 对父类的数据进行初始化
- 先在子类的有参构造方法中通过this访问本类的无参或有参构造方法;然后在本类的无参构造方法中用super(“xxx”);访问父类的有参构造方法。(不推荐)
在开发中,对于继承关系的构造方法访问都是用子类的无参构造方法访问父类的无参构造方法,用子类的有参构造方法访问父类的有参构造方法。
总结:继承关系中的构造方法访问问题就是从父类到子类的分层初始化!
继承中成员方法的关系
成员方法访问问题:
-
子类继承父类,子类的成员方法名和父类的成员方法名不一致的情况。
解决办法:分别访问
-
子类继承父类,子类出现了和父类一模一样的方法的情况。
解决办法:先找子类的方法,找到了就执行,如果子类没有这个方法,那么执行父类的方法。如果连父类的方法都没有,那系统就会报错!
定义成员变量的时机
定义成员变量,不是给一个需求,有了变量就立刻将其定义为成员变量。我们应该思考给出的变量能否描述现实世界真实事物的属性。如果可以,就定义为成员变量,否则就定义为局部变量。
**在实际开发中,优先考虑的是局部变量。**因为成员变量跟对象有关,它的范围(生命周期)比局部变量大。而局部变量随着方法调用而存在,随着方法调用结束而消失,非常节省内存空间。
练习:
需求:有一个长方形,键盘录入的长和宽,求面积和周长(使用面向对象的编程思想操作)
分析:长方形是一个事物,属性有长和宽,我们需要做的是求面积和周长。所以我们应该定义一个长方形类,将长length和宽width定义为成员变量,并将其私有化。成员方法定义求面积方法getArea()和求周长方法getPerimeter()。
实现:
//定义一个长方形类
class Rect{
//属性---成员变量
private int length;//长
private int width; //宽
//不需要提供get()--->提供setXXX(),赋值 或者通过有参构造方法赋值
public void setLength(int length) {
this.length = length;
}
public void setWidth(int width) {
this.width = width;
}
//定义成员方法:求面积
public int getArea(){
return length*width ;
}
//定义成员方法:求周长
public int getPerimeter(){
return (length+width)*2 ;
}
}
//测试类
public class ExtendsTest {
public static void main(String[] args) {
//创建键盘录入对象
Scanner sc = new Scanner(System.in) ;
//提示并录入数据
System.out.println("请您输入长方形的长:");
int length = sc.nextInt() ;
System.out.println("请您输入长方形的宽:");
int width = sc.nextInt() ;
//封装长方形类
Rect rect = new Rect() ;
rect.setLength(length) ;
rect.setWidth(width);
//求面积
System.out.println("这个长方形的面积是:"+rect.getArea());
System.out.println("这个长方形的周长是:"+rect.getPerimeter());
}
}
方法重写
在Java继承关系中,子类会继承父类的功能。但是子类在继承时并非“完全”继承,它仍然可以对父类方法进行改造和覆盖。当子类出现了和父类一模一样的方法声明,而且运行时子类写的方法将父类的方法覆盖(复写)的时候,就出现了叫做方法重写的现象。
方法重写与之前介绍的方法重载完全不同。方法重载是在同一个类中,两个或多个方法出现了方法名相同,参数列表不同的现象。而方法重写是在继承关系中,子类在继承的同时,对父类的方法进行改造和覆盖的现象。
方法重写的例子:
//定义一个手机类
class Phone{
//最基本的电话的功能
public void call(){
System.out.println("手机可以打电话了");
}
}
//子类:新手机
class NewPhone extends Phone{
//想沿用父类的功能,还有自己的特有功能
@Override
public void call(){
super.call() ;//调用父类的call方法
//子类自己的特有功能
System.out.println("现在可以看天气预报了");
System.out.println("还可以听音乐了");
}
}
//测试类
public class ExtendsDemo3 {
public static void main(String[] args) {
/* Phone phone = new Phone() ;
phone.call();*/
//创建子类对象
NewPhone np = new NewPhone() ;
np.call();
}
}
继承的练习
/**
* 需求:用继承实现“猫狗”案例
* 定义一个动物类(Animal),属性有姓名、年龄、颜色,行为有吃和睡
* 再定义猫类(Cat)和狗类(Dog),都继承自动物类
* 猫和狗吃的不一样,重写它们的方法
* 分别定义方法 猫有特有功能:玩毛线;狗有特有功能:看门
*/
class Animal {
private String name ;
private int age ;
private String color ;
public Animal() {
}
public Animal(String name, int age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public void eat(){
System.out.println("动物都需要吃饭") ;
}
public void sleep(){
System.out.println("动物都需要休息...");
}
}
class Cat extends Animal {
public Cat() {
//隐藏了super(),默认不写
}
public Cat(String name, int age, String color) {
super(name, age, color);
}
//猫的吃的不一样,重写父类的eat方法
@Override
public void eat() {
System.out.println("猫吃鱼...");
}
@Override
public void sleep() {
System.out.println("猫侧着睡觉...");
}
//特有功能
public void playGame(){
System.out.println("猫玩毛线球球...");
}
}
class Dog extends Animal {
//构造
public Dog() {
}
public Dog(String name, int age, String color) {
super(name, age, color);//间接访问的是父类的有参构造
}
//重写
@Override
public void eat() {
//super.eat();
System.out.println("狗吃骨头...");
}
@Override
public void sleep() {
System.out.println("狗趴着睡觉...");
}
//特有功能
public void lookDoor(){
System.out.println("狗看门...");
}
}
public class ExtendsTest {
public static void main(String[] args) {
//测试狗--养了一只狗
//方式1:无参构造方法+setXXX()/getXXX()
Dog dog = new Dog() ;
dog.setName("拉布拉多") ;
dog.setAge(3);
dog.setColor("棕色") ;
System.out.println("养的狗名称:"+dog.getName()+",年龄是:"+dog.getAge() +"它的颜色是:"+dog.getColor()) ;
dog.eat() ;
dog.sleep() ;
//特有功能
dog.lookDoor();
System.out.println("----------------------------------") ;
//测试猫--养了一只猫
//方式2:有参构造方法+getXXX()
Cat cat = new cat("橘猫",3,"白色") ;
System.out.println("养的猫名称:"+cat.getName()+",年龄是:"+cat.getAge() +"它的颜色是:"+cat.getColor()) ;
cat.eat() ;
cat.sleep() ;
//特有功能
cat.playGame();
}
}
总结
使用继承思想的注意事项
- 不要为了使用部分功能而使用继承思想,因为继承具有“局限性”。
- 不要多次使用继承,因为产生这种关系后,就存在“耦合”了!(高内聚低耦合原则)
对于第一条的举例解释:假设一个类A有名为show和show2的方法,另一个类B有名为show2和show3的方法,这两个类是不能产生继承关系的。因为B继承了A的show2方法,但是A的show方法,类B不需要!
使用继承思想的时机
如果一个类A是类B的一种或者类B是类A的一种,那么我们就使用"继承思想"。
继承核心思想:继承体现的是一种“is a”的关系!
注意:同一个包下不能出现相同的类名
final关键字
子类继承父类的时候,子类会出现方法重写的现象。但是,有的时候一些方法是不能被覆盖的。Java提供了一个关键字final,它是一个状态修饰符号,表示“最终的、无法更改的”。如果用final修饰成员方法,这个方法就不能被重写。
final关键字的特点
- final修饰类,该类不能被继承
- final修饰成员方法,该方法不能被重写
- final修饰成员变量,此时变量是一个常量——“自定义常量”
- final修饰成员变量,必须初始化
- final修饰的变量只能被赋值一次。
- final修饰局部变量,赋值一次后,不能再次赋值
补充:编译时期常量和运行时期常量
编译时期常量是指在编译时就能确定具体值的变量,这些变量包括被final修饰的基本数据类型和直接给出值的String;而运行时期常量指在JVM加载时才能确定具体值的变量,它包括被final修饰的引用数据类型,下面有两个例子。
public static final 基本数据类型 变量名 = 值 ; //编译时期常量,因为jvm不需要加载基本数据类型
public static final int a = 10 ;
public static final 引用数据类型 变量名 = new 类名() ; //运行时期常量,使用加载类的!
//静态实例 引用类型---不能在new了
public static final Integer i = new Integer(100);
//jvm加载的时候需要加载Integer类的。Integer类包装了基本类型int的值,是int类型的包装类类型,也属于引用数据类型 (jdk7 自动拆装箱)
博客难免会产生一些错误。如果写的有什么问题,欢迎大家批评指正。