【Java基础】——java面向对象(中)—继承、抽象类、接口


一、面向对象——继承

1、继承概述

①什么是继承?

继承是面向对象的一个重要方面。当多个类存在相同属性和行为时,将这些类抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只需要继承那个类即可。关键字extends表明正在构造的新生类派生于一个已存在的类。已存在的类被称为超类(superclss)、基类(base class)或父类(parent class);新类被称为子类(subclass)。“is-a”关系是继承的一个明显特征。

②通过extends关键字来实现类与类的继承,如:
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、什么是方法覆盖?(子父类中)方法覆盖是指在子类中,出现和父类一模一样的方法声明的时候,会运行子类的函数,这种现象称为覆盖操作。
方法覆盖会发生在有继承关系的父类和子类之间,而且是在子类类型中,子类继承到父类的方法之后,觉得方法实现已经不足以满足新一代的要求了,于是就给出了新的方法实现。
覆盖注意事项:
  • 子类方法覆盖父类方法时,子类权限必须大于等于父类中的权限。
  • 静态只能覆盖静态或者被静态覆盖。
c、如何判断方法是不是重载呢?
  • 方法名必须相同
  • 返回值类型可能不同
  • 参数列表必须不同:参数类型不同,参数个数不同,参数顺序不同。

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可以修饰类,方法,变量
*/

//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();
	}
}

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值