一、面向对象——继承
1、继承概述
①什么是继承?
继承是面向对象的一个重要方面。当多个类存在相同属性和行为时,将这些类抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只需要继承那个类即可。关键字extends表明正在构造的新生类派生于一个已存在的类。已存在的类被称为超类(superclss)、基类(base class)或父类(parent class);新类被称为子类(subclass)。“is-a”关系是继承的一个明显特征。
class 子类名 extends 父类名{}
有了继承以后,我们在定义一个类的时候,可以在一个已经存在的类的基础上,还可以定义自己的新成员。
但是,我们需要注意的是,在java中支持单继承,不直接支持多继承。但对c++中的多继承机制进行了改良。
a、单继承:一个子类只能有一个直接父类
b、多继承:一个子类可以有多个直接父类。(java中不允许)因为多个父类中有相同成员时,会产生调用的不确定性。
c、java支持多层继承(多重继承):如C继承B,B继承A,这样就出现了继承体系。
当我们需要使用一个继承体系时,我们首先要查看该体系中的顶层类,了解该体系的基本功能,然后再创建体系中的最子类对象,完成功能的使用。
③继承的设计原则:"高内聚低耦合"
所谓高内聚低耦合,简单的理解,内聚就是指自己完成某件事情的能力,耦合就是类与类之间的关系。我们在设计继承时候的原则就是:自己能完成的就不要麻烦别人,这样将来别人产生了修改,就对我的影响较小。由此可见:在开发中使用继承其实是在使用一把双刃剑。今天我们还是以继承的好处来使用,因为继承还有很多其他的特性。
④继承的好处
- 将多个类相同的成员可以放到一个类中,提高了代码的复用性。
- 如果功能的代码需要修改,修改一处即可,提高了代码的维护性。
- 让类与类之间产生了关系,是多态的前提。但这也造成了高耦合,也是其弊端之一。
/*
继承概述
*/
//使用继承前
/*
class Student {
public void eat() {
System.out.println("吃饭");
}
public void sleep() {
System.out.println("睡觉");
}
}
class Teacher {
public void eat() {
System.out.println("吃饭");
}
public void sleep() {
System.out.println("睡觉");
}
}
*/
//使用继承后,老师和学生都是人的范畴,抽取了老师和学生的共性功能,作为人的功能,让老师和学生去继承。
class Person {
public void eat() {
System.out.println("吃饭");
}
public void sleep() {
System.out.println("睡觉");
}
}
class Student extends Person {}
class Teacher extends Person {}
class ExtendsDemo {
public static void main(String[] args) {
Student s = new Student();
s.eat();
s.sleep();
System.out.println("-------------");
Teacher t = new Teacher();
t.eat();
t.sleep();
}
}
2、继承注意事项
①子类只能继承父类所有非私有的成员(成员方法和成员变量)
②子类不能继承父类的构造方法,但是可以通过super关键字去访问父类构造方法。
③不要为了部分功能去继承。
④当类之间出现了”is-a“的关系时,我们才可以去继承。不能因为两个类中有部分代码相同就使用继承,这是不对的。
代码演示如下:
/*
继承的注意事项:
class A {
public void show1(){}
public void show2(){}
}
class B {
public void show2(){}
public void show3(){}
}
//我们发现B类中出现了和A类一样的show2()方法,所以,我们就用继承来体现
class B extends A {
public void show3(){}
}
这样其实不好,因为这样你不但有了show2(),还多了show1()。
有可能show1()不是你想要的。
继承其实体现的是一种关系:"is a"。
采用假设法。
如果有两个类A,B。只有他们符合A是B的一种,或者B是A的一种,就可以考虑使用继承。
*/
class Father {
private int num = 10;
public int num2 = 20;
//私有方法,子类不能继承
private void method() {
System.out.println(num);
System.out.println(num2);
}
public void show() {
System.out.println(num);
System.out.println(num2);
}
}
class Son extends Father {
public void function() {
//num可以在Father中访问private
//System.out.println(num); //子类不能继承父类的私有成员变量
System.out.println(num2);
}
}
class ExtendsDemo3 {
public static void main(String[] args) {
// 创建对象
Son s = new Son();
//s.method(); //子类不能继承父类的私有成员方法
s.show();
s.function();
}
}
3、super关键字
①super关键字和this的用法很像,this代表的是对应的引用,而super代表父类存储空间的标识(父类引用)。
②super关键字的用法:
- 访问成员变量:super.成员变量
- 访问构造方法:super(……)
- 访问成员方法:super.成员方法()。
为什么子类所有的构造方法默认都会访问父类空参数的构造方法?
因为子类会继承父类的数据,可能还会使用父类的数据。所以,子类初始化之前,一定要先完成父类数据的初始化。为了实现这个效果,在子类构造的第一条语句上默认有一个super()。
/*
继承中构造方法的关系
A:子类中所有的构造方法默认都会访问父类中空参数的构造方法
B:为什么呢?
因为子类会继承父类中的数据,可能还会使用父类的数据。
所以,子类初始化之前,一定要先完成父类数据的初始化。
注意:子类每一个构造方法的第一条语句默认都是:super();
*/
class Father {
int age;
public Father() {
System.out.println("Father的无参构造方法");
}
public Father(String name) {
System.out.println("Father的带参构造方法");
}
}
class Son extends Father {
public Son() {
//super();
System.out.println("Son的无参构造方法");
}
public Son(String name) {
//super();
System.out.println("Son的带参构造方法");
}
}
class ExtendsDemo6 {
public static void main(String[] args) {
//创建对象
Son s = new Son();
System.out.println("------------");
Son s2 = new Son("林青霞");
}
}
④继承中成员方法的关系
a、当子父类中方法声明不一样的时候,通过子类对象去访问方法,这个很容易访问。
b、当子父类方法声明一样的时候,首先是通过子类对象去访问方法,先查找子类中有没有该方法,如果有该方法,就使用。如果子类中没有该方法,则去父类中查找有没有该方法,如果父类中有该方法,则使用。如果字父类中都没有该方法,则报错。
如下代码所示:
/*
继承中成员方法的关系:
*/
//父类
class Father {
public void show() {
System.out.println("show Father");
}
}
//子类
class Son extends Father {
public void method() {
System.out.println("method Son");
}
public void show() {
System.out.println("show Son");
}
}
class ExtendsDemo8 {
public static void main(String[] args) {
//创建对象
Son s = new Son();
s.show();//找子类。
s.method();//method son。先找子类中,子类有则调用。
//s.fucntion(); //找不到符号,子父类中都没有该方法。
}
}
⑤方法重载(overroad)和方法覆盖(override)a、什么是方法重载?(同一个类中)方法重载是指在同一个类中,出现方法名相同,参数列表不同的情况。
b、什么是方法覆盖?(子父类中)方法覆盖是指在子类中,出现和父类一模一样的方法声明的时候,会运行子类的函数,这种现象称为覆盖操作。
方法覆盖会发生在有继承关系的父类和子类之间,而且是在子类类型中,子类继承到父类的方法之后,觉得方法实现已经不足以满足新一代的要求了,于是就给出了新的方法实现。
覆盖注意事项:
- 子类方法覆盖父类方法时,子类权限必须大于等于父类中的权限。
- 静态只能覆盖静态或者被静态覆盖。
- 方法名必须相同
- 返回值类型可能不同
- 参数列表必须不同:参数类型不同,参数个数不同,参数顺序不同。
3、阻止继承:final类和方法
有时候,可能希望阻止人们利用某个类定义子类。不允许扩展的类被称为final类。如果在定义类的时候使用了final修饰符就表明这个类是final类。
①final关键字是最终的意思,可以修饰类,成员变量,成员方法。
- final关键字修饰的类不可以被继承。
- final修饰的方法不可以被覆盖。
- final修饰的变量是一个常量,只能被覆盖。
②类中的方法也可以被声明为final。如果这样做,子类就不能覆盖这个方法(final类中的所有方法自动称为final方法。)将方法或类声明为final主要是鉴于以下原因:
- 确保它们不会在子类中改变语义。例如:Calendar类中的getTime个setTime方法都声明为final。这表明Calendar类的设计者负责实现Date类与日历状态之间的转换,而不允许子类处理这些问题。同样的,String类也是final类,这意味着不允许任何人定义String的子类。换而言之,如果有一个String的引用,它引用的一定是一个String对象,而不可能是其它对象。
其实在程序中如果一个数据是固定的,那么直接使用这个数据就可以了。但是这样阅读性很差,所以就给该数据起个名称,而且这个变量名称的值不能再变化,所以加上final固定。
写法规范:常量所有字符都大写,如果多个单词,中间用"_"连接。
代码示范如下:
/*
final可以修饰类,方法,变量
*/
//final class Fu //无法从最终Fu进行继承
class Fu {
public int num = 10;
public final int NUM_2 = 20;
// Zi中的show()无法覆盖Fu中的show()
/* public final void show() {
System.out.println(num);
}
*/
}
class Zi extends Fu {
// Zi中的show()无法覆盖Fu中的show()
public void show() {
num = 100;
System.out.println(num);
//无法为最终变量num2分配值
//NUM_2 = 200;
System.out.println(NUM_2);
}
}
class FinalDemo {
public static void main(String[] args) {
Zi z = new Zi();//100
z.show();//20
}
}
④final关键字面试题
final修饰局部变量
- 在方法内部,该变量不可改变。
- 在方法声明上,如果是基本类型,则值不能改变;如果是引用类型,则是地址值不能改变。
/*
面试题:final修饰局部变量的问题
基本类型:基本类型的值不能发生改变。
引用类型:引用类型的地址值不能发生改变,但是,该对象的堆内存的值是可以改变的。
*/
class Student {
int age = 10;
}
class FinalTest {
public static void main(String[] args) {
//局部变量是基本数据类型
int x = 10;
x = 100;
System.out.println(x);//100
final int y = 10;
//无法为最终变量y分配值(基本类型)
//y = 100;
System.out.println(y);//10
System.out.println("--------------");
//局部变量是引用数据类型
Student s = new Student();
System.out.println(s.age);//10
s.age = 100;
System.out.println(s.age);//100
System.out.println("--------------");
//引用数据类型的地址值不可改变,但堆内存的值是可变的!
final Student ss = new Student();
System.out.println(ss.age);//10
ss.age = 100;
System.out.println(ss.age);//100
//重新分配内存空间
//错误:无法为最终变量ss分配值(引用类型)
//ss = new Student();
}
}
final修饰变量的初始化时机:在构造对象前完毕即可。
二、面向对象——抽象类
1、什么是抽象?
定义:抽象类就从多个事物中,将共性的、本质的东西提取出来。
例如:老师和学生,都是人,都具有如姓名、性别、年龄等属性。将其抽取出来,放置于继承关系较高层次的通用超类人中。
2、抽象类
①什么是抽象类?
Java中可以定义没有方法体的方法,该方法的具体实现由子类完成,该方法称为抽象方法,包含抽象方法的类就是抽象类。
抽象类的由来:多个对象都具备相同的功能,但是功能具体内容有所不同,那么在抽取过程中,只抽取了功能定义,并未抽取功能主体,那么只有功能声明,没有功能主题的方法称为抽象方法。
②抽象类的特点
- 方法只有声明没有实现时,该方法就是抽象方法,需要abstract修饰,抽象方法必须定义在抽象类中,该类也必须被abstract修饰。
- 抽象类不能被实例化,因为调用抽象方法没有意义。抽象类按照多台的方式,由具体的子类实例化。
- 抽象类必须有其子类覆盖了所有的抽象方法后,该子类才可以初始化,否则这个子类还是抽象类。
- 抽象类不一定有抽象方法,有抽象方法的类一定是抽象类。
/*
抽象类的概述:
动物不应该定义为具体的东西,而且动物中的吃,睡等也不应该是具体的。
我们把一个不是具体的功能称为抽象的功能,而一个类中如果有抽象的功能,该类必须是抽象类。
抽象类的实例化其实是靠具体的子类实现的。是多态的方式。
People p = new Student();
*/
//abstract class People //抽象类的声明格式
abstract class People {
//抽象方法
//public abstract void eat(){} //空方法体,这个会报错。抽象方法不能有主体
public abstract void eat();
public People(){}
}
//子类是抽象类
abstract class Teacher extends People {}//没有实现父类的抽象方法。还是抽象类。
//子类是具体类,重写抽象方法
class Student extends People {
public void eat() {
System.out.println("学生吃饭");
}
}
class AbstractDemo {
public static void main(String[] args) {
//创建对象
//people是抽象的; 无法实例化
//People p = new People();
//通过多态的方式
People p = new Student();
p.eat();
}
}
3、抽象类的几个问题
①抽象类中有构造方法吗?有,用于给子类对象进行初始化。
②抽象类可以定义非抽象方法吗?
可以。但是很少见,目的是不让该类创建对象,AWT的适配器对象就是这种类,通常这个类中的方法有方法体,但是却没有内容。
③抽象的关键字不可以和哪些关键字共存?
private不行,static不行,final不行。
原因在于:
- final:被final修饰的类不能有子类。而abstract修饰的类一定是父类。
- private:抽象类中的私有的抽象方法,不能被子类所知,也就无法复写,而抽象方法的出现就是要被复写。
- static:如果static修饰抽象方法,那么对象都不用new了。直接类名.方法调用就行了。
/*
一个类如果没有抽象方法,可不可以定义为抽象类?如果可以,有什么意义?
A:可以。
B:不让创建对象。
abstract不能和哪些关键字共存?
private 冲突
final 冲突
static 无意义
*/
abstract class Fu {
//public abstract void show();
//非法的修饰符组合: abstract和private
//private abstract void show();
//非法的修饰符组合
//final abstract void show();
//非法的修饰符组合
static abstract void show();
public static void method() {
System.out.println("method");
}
}
class Zi extends Fu {
public void show() {}
}
class AbstractDemo {
public static void main(String[] args) {
Fu.method();
}
}
4、抽象类和一般类的异同点
①相同点:抽象类和一般类都是用来描述事物的,都是内部定义了成员。②不同点:
- 一般类有足够的信息描述事物;抽象类中描述的信息可能不足。
- 一般类不能定义抽象方法,只能定义非抽象方法;抽象类可以定义抽象方法,也可以定义非抽象方法。
- 一般类可以实例化;抽象类不可以实例化。
三、接口
1、接口的概念
接口可以被看成是一个特殊的抽象类。当一个抽象类中的方法都是抽象的时候,这是可以将抽象类用另一种形式来定义和表示,就是接口(interface)。定义接口使用的关键字不是class,而是interface,接口当中的常见成员,而且这些成员都有固定的修饰符:全局变量,抽象方法。
2、接口的特点
①接口用关键字interface表示。格式:interface接口名{}。
②类实现接口用implements表示。格式:class类名implements接口名{}。
③接口不是类,不能实例化。尤其不能用new运算符实例化一个接口。按照多态的方式,由具体的子类实例化。其实这也是多态的一种,接口多态。
④接口的子类要么是抽象类,要么重写接口中的所有方法。
⑤不能构造接口的对象,但却能声明接口的变量。如:Comparable x;
⑥接口变量必须引用实现了接口的类对象。如:x = new XXXX();
⑦接口中不能包含实例域或静态方法,但却可以包含常量。
3、接口成员的特点
①成员变量:只能是常量。默认修饰符是public static final。
②构造方法:没有,因为接口主要是扩展功能的,而没有具体存在
③成员方法:只能是抽象方法,默认修饰符public abstract。
4、接口必须掌握的知识
- 接口当中的成员都是公共的,是自动属于public的。因此,在接口方法声明中,可以不写public。
- 类与类之间的是继承关系(extends);类与接口之间是实现关系(implments)。
- 接口不可以实例化,只能由实现类接口的子类并覆盖了接口中所有的抽象方法后,该子类才可以实例化,否则,这个子类就是一个抽象类。
- 接口实现的步骤:a、将类声明为实现给定的接口。b、对接口中的所有方法进行定义。
- 在java中不直接支持多继承,因为会出现调用的不确定性,所以java将多继承机制进行了改良,在java中可以多实现,即一个类可以实现多个接口。
- 一个类在继承另一个类的同时,还能实现多个接口。接口的出现避免了单继承的局限性。
- 接口与接口之间可以继承,而且接口可以多继承。
5、抽象类和接口的异同点
①相同点:都是不断向上抽取而来的。
②不同点:
- 抽象类需要被继承,而且只能单继承;接口需要被实现,而且能多实现。
- 抽象类中可以定义抽象方法和非抽象方法,子类继承后,可以直接使用非抽象方法;接口中只能定义抽象方法,必须由接口去实现。
- 抽象类的继承是is-a关系,在定义该体系内的基本共性内容;接口的实现是like-a关系,在定义体系的额外功能。
/*
老师和学生案例,加入抽烟的额外功能
分析:从具体到抽象
老师:姓名,年龄,吃饭,睡觉
学生:姓名,年龄,吃饭,睡觉
由于有共性功能,我们提取出一个父类,人类。
人类:
姓名,年龄
吃饭();
睡觉(){}
抽烟的额外功能不是人或者老师,或者学生一开始就应该具备的,所以,我们把它定义为接口
抽烟接口。
部分老师抽烟:实现抽烟接口
部分学生抽烟:实现抽烟接口
实现:从抽象到具体
使用:具体
*/
//定义抽烟接口
interface Smoking {
//抽烟的抽象方法
public abstract void smoke();
}
//定义抽象人类
abstract class Person {
//姓名
private String name;
//年龄
private int age;
public Person() {}
//构造器初始化
public Person(String name,int age) {
this.name = name;
this.age = age;
}
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 abstract void eat();
//睡觉睡觉方法{}
public void sleep() {
System.out.println("睡觉觉了");
}
}
//具体老师类
class Teacher extends Person {
public Teacher() {}
public Teacher(String name,int age) {
super(name,age);
}
public void eat() {
System.out.println("吃大白菜");
}
}
//具体学生类
class Student extends Person {
public Student() {}
public Student(String name,int age) {
super(name,age);
}
public void eat() {
System.out.println("吃红烧肉");
}
}
//抽烟的老师
class SmokingTeacher extends Teacher implements Smoking {
public SmokingTeacher() {}
public SmokingTeacher(String name,int age) {
super(name,age);
}
//抽烟方法
public void smoke() {
System.out.println("抽烟的老师");
}
}
//抽烟的学生
class SmokingStudent extends Student implements Smoking {
public SmokingStudent() {}
public SmokingStudent(String name,int age) {
super(name,age);
}
//实现抽烟方法
public void smoke() {
System.out.println("抽烟的学生");
}
}
class InterfaceTest2 {
public static void main(String[] args) {
//测试学生
SmokingStudent ss = new SmokingStudent();
ss.setName("黄祥");
ss.setAge(23);
System.out.println(ss.getName()+"---"+ss.getAge());
ss.eat();
ss.sleep();
ss.smoke();
System.out.println("-------------------");
//测试老师
SmokingTeacher st = new SmokingTeacher();
st.setName("刘老师");
st.setAge(45);
System.out.println(st.getName()+"---"+st.getAge());
st.eat();
st.sleep();
st.smoke();
}
}