——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-
1.概述:提取多个类中的共性内容,组成一个新的类,这个新类和原来类之间就产生了一种关系,并且原来的多个类都具备新类中的内容,这种关系就称为继承。
举例:现实生活中当提到猫,狗时,我们都清楚它们是一种动物,那么就可以说猫,狗继承自动物。
类似这种: is-a关系都可以称为继承关系,被继承的类称为父类或超类(father/super class),继承自父类的类称为子类或基类(child/base class)。
例如:Cat is-a animal, 小明 is-a 学生…
注:判断是否使用继承:类之间是否具备is-a关系
2.继承的书写格式
Java中通过extends 关键字来实现类与类之间的继承关系
格式:class 子类名 extends 父类名 { }
示例:
class Person {}
class Student extends Person {}
class Teacher extends Person {}
注:该示例仅仅用于说明Java的继承书写格式,因此没有给出类的具体实现。
3.继承的简单案例
需求:
<1>定义一个类Person
该类中包含两个成员变量:name, age
两个构造方法:空参构造,带参构造
一个普通方法:show():用于打印所有成员变量
<2>定义一个类Student继承Person
<3>定义一个测试类Demo,在测试类中测试两个类。
代码:
/* * Person类:父类 */
public class Person {
/* * 成员变量 */
String name;
int age;
//空参构造
public Person() {
}
//带参构造
public Person(String name, int age) {
this.name = name;
this.age = age;
}
//普通方法show()
public void show() {
System.out.println("Person:" + name + "," + age);
}
}
/* * Student类:子类 * 在学生类中没有定义任何成员,主要为了说明继承的特点 */
public class Student extends Person {
}
public class Demo {
/* * 测试类:创建Student类对象,并通过Student对象访问Person类中的成员 * 1.访问Person类中的成员变量 * 2.调用Person类中的成员方法 */
public static void main(String[] args) {
Student s = new Student();
// 1.访问Person类中的成员变量
s.name = "小明";
s.age = 18;
System.out.println(s.name);
System.out.println(s.age);
// 2.调用Person类中的成员方法
s.show();
}
}
测试结果:
小明
18
Person:小明,18
4.继承的好处和弊端
好处:
<1>提高了代码的复用性
<2>提高了代码的可维护性
<3>让类与类之间产生了关系,是多态的前提条件
弊端:
<1>打破了类的封装性
<2>使类之间的耦合性增强,当父类发生改变时,会对子类产生影响
程序开发的原则:低耦合,高内聚
耦合是指类与类之间的关系,内聚是指类独立完成某项功能的能力
5.Java中继承的特点
<1>Java中只支持单继承,不支持多继承
<2>Java可以支持多层继承
代码解释:
class A{
}
class B{
}
/* * 下面的代码编译无法通过 * 原因:Java只支持单继承,C无法既继承A,又继承B */
class C extends A,B{
}
class A{
}
class B extends A{
}
/* * 下面代码可以编译通过 * 原因:Java支持多层继承,C通过继承B间接继承了A */
class C extends B{
}
6.继承中的注意事项
<1>子类不能继承父类的私有成员,因为私有成员只能在本类中访问
代码验证:
class Father{
/*在父类中定义两个私有成员*/
private int num = 10;
private void method(){
System.out.println("Father method.");
}
}
//子类中没有成员
class Child extends Father{
}
public class ExtendDemo{
public static void main(String[] args) {
//在测试类中创建子类对象
Child c = new Child();
//通过子类对象变量调用父类的私有成员
//编译报错: num has private access in Father
c.num = 200;
//编译报错: cannot find symbol c.method();
c.method();
}
}
<2>子类无法继承父类的构造方法,但可以通过super访问
<3>不要为了部分功能而使用继承,需要确定类之间是否具备is-a关系
7.继承关系中子父类成员的关系
<1>成员变量的关系:
问题1:如何在子类方法中访问父类中的非私有成员变量?
代码解答:
情况1:子父类中成员变量不同名的情况
class Father {
int a = 100;
}
class Child extends Father {
int b = 200;
public void childMethod() {
System.out.println("访问子类中的变量:" + b);
System.out.println("访问父类中的变量:" + a);
}
}
class ExtendDemo2 {
public static void main(String[] args) {
Child c = new Child();
c.childMethod();
}
}
情况2:子类中成员变量与父类成员变量同名的情况
class Father {
int a = 100;
}
class Child extends Father {
int a = 200;
public void childMethod() {
int a = 300;
System.out.println("访问子类中的局部变量:" + a);
System.out.println("访问子类中的成员变量:" + this.a);
System.out.println("访问父类中的成员变量:" + super.a);
}
}
class ExtendDemo2 {
public static void main(String[] args) {
Child c = new Child();
c.childMethod();
}
}
运行结果:
由上面的示例代码可知,要在子类中访问父类成员变量,可以使用super关键字
变量的就近访问原则:在方法内使用一个变量时,先在方法中寻找该变量,若没找到则在本类成员变量中寻找该变量,若仍然找不到则在父类成员变量中寻找该变量,以上位置都无法找到该变量,则报错。
类似的方法的调用也有就近访问原则,先在调用方法的对象中查找该方法,没有则到父类中找该方法,然后间接父类,依次类推,没找到就报错。
<2>构造方法的关系
A.虽然子类无法继承父类的构造方法,但子类的构造方法默认会去访问父类的空参构造方法,这样做的原因在于初始化子类数据之前先初始化父类的数据
代码示例:
class Father{
private int a;
private int b;
public Father() {
System.out.println("父类空参构造方法被调用.");
}
public Father(int a, int b) {
this.a = a;
this.b = b;
System.out.println("父类带参构造方法被调用.");
}
}
class Child extends Father {
public Child() {
System.out.println("子类空参构造方法被调用.");
}
public Child(int a, int b) {
System.out.println("子类带参构造方法被调用.");
}
}
public class ExtendDemo2{
public static void main(String[] args) {
Child c = new Child();
System.out.println("---------------");
Child c2 = new Child(1,2);
}
}
运行结果:
从程序的运行结果可以看到,在调用子类构造方法创建对象时,默认会调用父类的空参构造方法。
B.父类中如果没有空参构造方法,那么子类需要显示调用父类的其他构造方法,通过super语句:super(参数…);
代码示例:对上面的代码示例做了如下修改
class Father{
private int a;
private int b;
//注释掉父类的空参构造
// public Father() {
// System.out.println("父类空参构造方法被调用.");
// }
//保留父类的带参构造
public Father(int a, int b) {
this.a = a;
this.b = b;
System.out.println("父类带参构造方法被调用.");
}
}
class Child extends Father {
public Child() {
//子类空参构造中显示调用父类带参构造
super(1,2);
System.out.println("子类空参构造方法被调用.");
}
public Child(int a, int b) {
//子类带参构造中显示调用父类带参构造
super(1,2);
System.out.println("子类带参构造方法被调用.");
}
}
public class ExtendDemo2{
public static void main(String[] args) {
Child c = new Child();
System.out.println("---------------");
Child c2 = new Child(1,2);
}
}
运行结果:
在上面的代码中如果不通过super语句显示调用父类的带参构造方法,则程序无法编译通过。
因此,子类的构造方法中一定会调用父类的构造方法。
super和this关键字对比:
this代表本类对象的引用,super代表父类对象的引用
this.成员变量(成员方法) : 在类中调用本类成员
super.父类成员 : 在子类中调用父类成员
this(参数) : 在本类构造方法中调用本类其他构造方法
super(参数) : 在子类构造方法中调用父类构造方法
<3>成员方法的关系
方法重写:子类中出现了和父类中一模一样的方法声明,即返回值类型,方法名,参数列表都相同,这种情况称为方法重写,或方法覆盖
使用特点示例:
思考如下代码:
class Father{
//在父类中定义方法method()
public void method() {
System.out.println("父类中的方法");
}
}
class Child extends Father {
//在子类中定义与父类相同声明的方法method()
public void method() {
System.out.println("子类中的方法");
}
}
public class ExtendDemo2{
public static void main(String[] args) {
Child c = new Child();
//通过子类对象调用方法method()
c.method();
}
}
运行结果:
从运行结果发现,最终调用的是子类的方法,而不是父类的方法,这种现象就是方法的重写。
方法重写的应用:当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法,这样,即沿袭了父类的功能,又定义了子类特有的内容
方法重写的注意事项:
父类中私有的方法不能重写,因为父类的私有方法子类无法继承;
子类重写父类方法时访问权限不能更低,最好与父类一致;
父类静态方法,子类也必须通过静态方法进行重写。
8.final 关键字
final 是最终的意思,可以用来修饰类,成员变量,成员方法
被final修饰的类无法被继承
被final修饰的变量只能被赋值一次,之后无法再改变,即称为常量
被final修饰的方法无法被子类重写
final修饰基本类型变量时,是值不能被改变;修饰引用类型变量时,是地址不能被改变
1.概述
多态是指某一个事物在不同时刻表现出来的不同状态。
例如:水可以有气态,液态,固态,但它们的本质都是水
动物可以指猫,狗,老虎…..虽然它们名字不同,但都属于动物的一种
那么Java中如何体现多态呢?
下面用一个需求的两种解决方案来说明:
需求:分别定义一个猫类,狗类,老虎类,鸟类,来模拟现实中的动物,这些动物都有一个行为叫进食,创建它们的对象并让所有动物进食。
分析:很显然每一种动物都有各自喜欢吃的食物,所以虽然它们都有吃的行为,但具体的行为方式却不尽相同。
代码实现:非多态的方式
//定义一个猫类
class Cat{
public void eat() {
System.out.println("猫吃鱼");
}
}
//定义一个狗类
class Dog{
public void eat() {
System.out.println("狗吃骨头");
}
}
//定义一个老虎类
class Tiger {
public void eat() {
System.out.println("老虎吃肉");
}
}
//定义一个鸟类
class Bird {
public void eat() {
System.out.println("鸟吃虫子");
}
}
class DuotaiDemo{
public static void main(String[] args) {
/* * 创建4种动物的对象 */
Cat cat = new Cat();
Dog dog = new Dog();
Tiger tiger = new Tiger();
Bird bird = new Bird();
/* * 调用各自的进食方法 */
cat.eat();
dog.eat();
tiger.eat();
bird.eat();
}
}
分析:通过结果,我们很开心的发现这段程序让所有动物吃到了想吃的东西,那么接下来需求改变了,又来了其他动物,马,大象,猪……不计其数的动物都要吃各自的喜欢的食物,我们当然可以用上面的代码去解决,再添加新的类,然后创建新动物类的对象,调用进食方法,但是可以想象代码的长度,而且每加入一个新的动物都要对代码做大量修改。
下面用一种更好的方式来改进上面的代码:
由于每种动物都要进食,而且它们都属于动物类型的一种,那么可以定义一个动物类,让所有的动物继承自这个类。
代码实现:多态方式
//定义一个动物类
class Animal {
//并没有给出到底吃什么
public void eat() {
}
}
//定义一个猫类,继承动物类
class Cat extends Animal{
public void eat() {
System.out.println("猫吃鱼");
}
}
//定义一个狗类,继承动物类
class Dog extends Animal{
public void eat() {
System.out.println("狗吃骨头");
}
}
//定义一个老虎类,继承动物类
class Tiger extends Animal{
public void eat() {
System.out.println("老虎吃肉");
}
}
//定义一个鸟类,继承动物类
class Bird extends Animal{
public void eat() {
System.out.println("鸟吃虫子");
}
}
class DuotaiDemo{
public static void main(String[] args) {
/* * 创建一个动物类型的数组 */
Animal[] animals = new Animal[4];
//将动物数组中的元素赋值为特定的动物对象
animals[0] = new Cat();
animals[1] = new Dog();
animals[2] = new Tiger();
animals[3] = new Bird();
/* * 让所有动物进食 */
for(int i=0; i
理解上面代码的关键就在于Animal类型的引用实际指向的对象是Animal的子类对象,而这正式多态的意义所在。可以看到通过多态改进后的代码可维护性和可扩展性都大大增强了,此时如果再加入其他动物类,只需要让新的动物继承Animal,重写eat()方法,然后将新动物的对象加入animals数组即可。
由上面的例子得出多态的前提条件:
有继承或者实现关系
有方法重写
父类引用指向子类对象
2.多态的好处和缺点
好处:提高了代码的可维护性和可扩展性
缺点:不能通过父类的引用使用子类特有的功能
向上转型:当父类引用指向子类对象时,编译器会将子类对象当作父类使用
向下转型:当需要在多态中使用子类特有方法时,需要将父类引用强制转型为子类引用,但这种操作的前提是必须保证父类引用实际指向的是子类对象,否则会发生类型转换异常。
3.多态中成员访问的特点
通过多态访问成员变量:通过父类的引用变量只能访问父类的成员变量,而不能访问子类的成员变量,否则编译不通过。
通过多态访问成员方法:通过父类引用只能调用父类中定义的方法,当子类重写了该方法时,则调用子类重写后的方法。无法调用子类特有的方法。
通过多态访问静态方法:因为静态的成员都是与类相关的,所有不存在多态的问题,通过类名调用即可。
构造方法:创建子类对象时需要调用子类构造方法,而子类构造方法中会默认调用父类构造方法。
1.抽象方法的概念:没有方法体的方法就是抽象方法,即没有方法的具体实现。
格式:修饰符 abstract 方法名 (参数列表) ;
2.抽象类的概念:被abstract关键字修饰的类称为抽象类。
凡是包含抽象方法的类必须定义为抽象类,但抽象类中不一定包含抽象方法。
3.抽象类的特点:
<1>抽象类和抽象方法必须用abstract关键字修饰
<2>抽象类中不一定包含抽象方法,但有抽象方法的类必须是抽象类
<3>抽象类不能实例化,但可以有构造方法
<4>抽象类的子类要么声明为抽象类,要么重写父类所有抽象方法。
除了以上四个特点抽象类和普通类没有什么区别,也可以定义成员变量和成员方法。
一个简单的抽象类案例:
/* 需求:定义一个抽象类Person 成员变量:姓名,年龄 成员方法:吃饭,睡觉 定义两个类继承Person,一个Student,一个Teacher Student有特有方法:study Teacher有特有方法:teach */
abstract class Person {
//成员变量
private String name;
private int age;
//空参构造
public Person() {}
//带参构造
public Person(String name, int age) {
this.name = name;
this.age = age;
}
//成员变量的getXxx()/setXxx()方法
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 abstract void sleep();
}
class Teacher extends Person {
public Teacher(){}
public Teacher(String name, int age) {
super(name, age);
}
//重写父类的抽象方法eat()
public void eat() {
System.out.println(getName() + " is eating.");
}
//重写父类的抽象方法sleep()
public void sleep() {
System.out.println(getName() + " is sleeping.");
}
//子类特有方法
public void teach() {
System.out.println(getName() + " is teaching.");
}
}
class Student extends Person {
public Student(){}
public Student(String name, int age) {
super(name, age);
}
//重写父类的抽象方法eat()
public void eat() {
System.out.println(getName() + " is eating.");
}
//重写父类的抽象方法sleep()
public void sleep() {
System.out.println(getName() + " is sleeping.");
}
//子类特有方法
public void study() {
System.out.println(getName() + " is study.");
}
}
//测试类
class PersonDemo {
public static void main(String[] args) {
Teacher t = new Teacher("比向东老师", 18);
Student s = new Student("小明", 6);
t.eat();
t.sleep();
t.teach();
s.eat();
s.sleep();
s.study();
}
}
4.抽象类的几个小问题
<1>抽象类有构造方法,不能实例化,那么构造方法有什么用?
答:用于子类访问父类数据的初始化
<2>一个类如果没有抽象方法,却定义为了抽象类,有什么用?
答:为了禁止创建该类的对象
<3>abstract不能和哪些关键字共存?
答:final,private,static
1.概述
在现实生活中有些人抽烟,有些人不抽烟,那么当我们要在Java中描述一个人类的时候(Person类),如何去表示哪些人抽烟哪写人不抽烟呢?
由于抽烟仅仅是一个行为,而这个行为不是所有人都具备,那么就可以将抽烟这个行为定义为一个接口,然后由抽烟的人去实现这个接口。
从上面的例子可以知道,当需要为某一个类添加扩展功能时,就需要为这个扩展功能定义一个接口,然后由需要扩展的类来实现具体的功能。
2.接口的特点
成员特点
<1>成员变量:只能是常量
接口中成员变量都有默认修饰符:public static final
<2>构造方法:没有构造方法
<3>成员方法:只能是抽象方法
接口中成员方法默认修饰符:public abstract
使用特点:
<1>定义一个接口时,用关键字interface修饰:
interface 接口名{ }
<2>一个类实现接口时,用implements 关键字
class 类名 implements 接口名 {}
<3>接口不能实例化
<4>接口的实现类特点:可以是抽象类或具体类,如果是具体类,必须重写(或者说实现更恰当)接口中所有方法
接口的简单示例
interface Smoke {
public abstract void smoke();
}
interface Study {
public abstract void study();
}
class Worker implements Smoke {
public void smoke(){
System.out.println("工人抽烟");
}
}
class Student implements Study{
public void study() {
System.out.println("学生学习");
}
}
public class InterfaceDemo {
public static void main(String[] args) {
Worker w = new Worker();
w.smoke();
Student s = new Student();
s.study();
}
}
3.类与类,类与接口,接口与接口之间的关系
<1>类与类之间:继承关系,只能单继承,可以多层继承
<2>类与接口之间:实现关系,可以单实现,也可以多实现
还可以在继承一个类的同时实现多个接口
<3>接口与接口之间:继承关系,可以单继承,也可以多继承
1.概念:把类定义在另一个类的内部,该类就被称为内部类
举例:
class A {
class B{
}
}
如上面代码所示,类B就是A的内部类,其实内部类是类中成员的一种,类中的成员有成员变量,成员方法,当然也可以有成员类,就像人的体内有心脏,肝脏,那么心脏就属于人这个类的内部类。
2.内部类的访问规则
<1>在内部类中可以直接访问外部类的成员,包括私有成员,因为内部类本身也属于类的成员
<2>外部类要想访问内部类的成员,必须创建内部类的对象
3.内部类的分类
<1>成员内部类:位于类中方法外,和成员变量位置相同
创建内部类对象的方式:
非静态成员内部类:
外部类名.内部类名 对象名 = new 外部类名.new 内部类名();
静态成员内部类:
外部类名.内部类名 对象名 = new 外部类名.内部类名();
<2>局部内部类:位于方法体内
局部内部类访问局部变量必须加final修饰。
4.匿名内部类
局部内部类的简化形式
前提:存在一个类或者接口
格式:
new 类名或接口名(){
重写方法
}
本质:匿名内部类的本质是继承该类或者实现该接口的子类的匿名对象。
代码示例:
interface Person {
public abstract void study();
}
class PersonDemo {
public void method(Person p) {
p.study();
}
}
class PersonTest {
public static void main(String[] args) {
PersonDemo pd = new PersonDemo();
pd.method(new Person() {
public void study() {
System.out.println("好好学习,天天向上");
}
});
}
}
包其实就是文件夹
1.作用:用于区分同名的类,并按照功能或模块对类进行分类管理
**2.定义方式:**package 包名;
注:多级包用 . 分隔
注意事项:
<1>package语句必须在文件中的第一条有效语句
<2>在一个java文件中,只能有一个package
<3>如果没有package,默认就是无包名
3.带包的编译和运行:
javac -d . HelloWorld.java
4.导包
<1>Java就提供了关键字import用于包的导入,如果在类中需要用到其他包中的类,则用import将需要的类导入。
<2>格式
import 包名…类名;
import 包名…*;(不建议)
<3>package,import,class的顺序
package > import > class