4.继承
4.1 继承的概述
继承:
1) 提高代码的复用性
2) 让类与类之间产生了关系,有了这个关系,才有了多态的特性
Java语言中:java只支持单继承,不支持多继承。因为多继承容易带来安全隐患,当多个父类中定义了相同功能,但功能内容不同时,子类对象不确定要运行哪一个。但java保留这种机制,并用另一种形式来完成表示—多实现。
java支持多层继承,即B继承A,C继承B,C中包含A、B中的功能
多重继承的出现,就有了继承体系。体系中的顶层父类是通过不断向上抽取而来的。它里面定义的该体系最基本最共性内容的功能。
所以,一个体系要想被使用,直接查阅该系统中的父类的功能即可知道该体系的基本用法。那么想要使用一个体系时,需要建立对象。建议建立最子类对象,因为最子类不仅可以使用父类中的功能。还可以使用子类特有的一些功能。
子父类出现后,类中的成员都有了哪些特点:
1:成员变量。
当子父类中出现一样的属性时,子类类型的对象,调用该属性,值是子类的属性值。
如果想要调用父类中的属性值,需要使用一个关键字:super
This:代表是本类类型的对象引用。
Super:代表是子类所属的父类中的内存空间引用。
注意:子父类中通常是不会出现同名成员变量的,因为父类中只要定义了,子类就不用在定义了,直接继承过来用就可以了。
2:成员函数。
当子父类中出现了一模一样的方法时,建立子类对象会运行子类中的方法。好像父类中的方法被覆盖掉一样。所以这种情况,是函数的另一个特性:覆盖(复写,重写)
覆盖的注意事项:
1) 子类覆盖父类,必须保证子类权限大于等于父类权限(默认权限不能覆盖private权限,因为此时子类无法读取到父类中private的内容,此时不算覆盖,而是新添加),才可以覆盖,否则编译失败
2) 静态只能覆盖静态
3:构造函数。
在对子类对象进行初始化时,父类的构造函数也会运行,那是因为子类的构造函数默认第一行有一条隐式的语句super();
super():会访问父类中空参数的构造函数。而且子类中所有的构造函数默认第一行都是super()
super语句一定定义在子类构造函数的第一行。构造函数中只能有this()和super()语句中的一个
为什么子类一定要访问父类中的构造函数?
因为父类中的数据,子类可以直接获取,所以子类对象在建立时,需要先查看父类是如何对这些数据进行初始化的。所以子类在对象初始化时,要先访问一下父类中的构造函数。如果要访问父类中指定的构造函数,可以通过手动定义super语句的方式来指定。
子类的实例化过程:
子类所有的构造函数,默认都会访问父类中空参数的构造函数,因为子类每一个构造函数内的第一行都有一句隐式super()
当父类中没有空参数的构造函数时,子类必须手动通过super句形式来指定要访问父类中的构造函数。
子类的构造函数第一行也可以手动指定this语句来访问本类中的构造函数。子类中至少会有一个构造函数会访问父类中的构造函数
代码示例:
class Super
{
int i=0;
Super(){} //如果子类中没有super(s); 则必须有此函数
public Super(String s)
{
i=1;
}
}
class Demo extends Super
{
public Demo(String s)
{
//super(s); //当父类中没有空参数的构造函数时,需执行此句
i=2;
}
public static void main (String[] args)
{
Demo d = new Demo("yes");
System.out.println(d.i);
}
}
//父类中必须建立Super(){}(空参数的构造函数),或者子类中要有super(s);语句来调用父类中的构造函数,否则编译失败
4.2 final关键字
final是修饰符:
1) 可以修饰类,函数,变量
2) 被final修饰的类不可以被继承。为了避免被继承,被子类复写功能。
3) 被final修饰的方法不可以被覆盖
4) 被final修饰的变量是一个常量,只能赋值一次,既可以修饰成员变量,又可以修饰局部变量
5) 内部类定义在类中的局部位置上时,只能访问该局部被final修饰的局部变量
当在描述事物时,一些数据的出现值是固定的,那么这时为了增强阅读性,都给这些值起个名字,方便阅读。而这个值不需要改变,所以加上final修饰。作为常量,常量的书写规范所有字母都大写,如果由多个单词组成,单词间通过 _ 连接。
4.3 抽象类
当多个类中出现相同功能,但是功能主题不同,这时可以进行向上抽取。这时,只抽取功能定义,而不抽取功能主体。
如:
abstract class Student
{
abstractvoid study();
}
抽象类的特点:
1) 抽象方法一定在抽象类中
2) 抽象方法和抽象类都必须被abstract关键字修饰
3) 抽象类不可以用new创建对象,即抽象类不可以实例化,因为调用抽象方法没意义
4) 抽象类中的方法要被使用,必须由子类复写其所有的抽象方法后,建立子类对象调用。如果子类只覆盖了部分抽象方法,那么该子类还是一个抽象类
抽象类的细节:
1)抽象类和一般类没有太大的不同。该如何描述事物,就如何描述事物,只不过该事物出现了一些看不懂的东西。这些不确定的部分也是该事物的功能,需要明确出现,但是无法定义主体。通过抽象方法来表示。
2)抽象关键字abstract和哪些不可以共存?final , private , static
3)抽象类中有构造函数,用于给子类对象进行初始化
4)抽象类中可以不定义抽象方法,这样做仅仅是不让该类建立对象。
抽象类的应用:模板方法设计模式
在定义功能时,功能的一部分时确定的,但是有一部分时不确定的,而确定的部分在使用不确定的部分。那么这时就将不确定的部分暴露出去,由该类的子类去完成
//获取程序运行时间:
//获取时间:System.currentTimeMillis();
abstract class GetTime
{
public final void getTime() //加上final,使得该方法无法复写
{
long start = System.currentTimeMillis();
runcode();
long end = System.currentTimeMillis();
System.out.println("毫秒:"+(end-start));
}
public abstract void runcode(); //具体功能不确定,由子类确定
}
class SubTime extends GetTime
{
public void runcode()
{
for (int x=0;x<4000 ; x++)
{
System.out.print(x);
}
}
}
class TimeDemo
{
public static void main(String[] args)
{
SubTime sb = new SubTime();
sb.getTime();
}
}
4.4 接口
Interface { }
接口定义时,格式特点:
1) 接口中常见定义:常量,抽象方法
2) 接口中的成员都有固定修饰符
常量:public static final
方法:public abstract
接口的特点:
1)接口中的成员都是public修饰的。接口是不可以创建对象的,因为有抽象方法。
2)Implements:类和接口之间的关系不是继承,而是实现。class Test implements Inter { }。
子类对接口中的抽象方法全部覆盖后,子类才可以实例化,否则子类是一个抽象类
3)接口可以被类多实现,也是对多继承不支持的转换形式,java支持多实现,因为接口中没有方法主体,由子类来定义方法具体功能。
class Test implements Inter,Inter1
4)接口与接口之间是继承关系,并且接口可以多继承。类可以继承一个类的同时实现多个接口,所以接口的出现避免了单继承的局限性。还可以将类进行功能的扩展。
抽象类与接口:
抽象类:一般用于描述一个体系单元,将一组共性内容进行抽取,特点:可以在类中定义抽象内容让子类实现,可以定义非抽象内容让子类直接使用。它里面定义的都是一些体系中的基本内容。
接口:一般用于定义对象的扩展功能,是在继承之外还需这个对象具备的一些功能。
抽象类和接口的共性:都是不断向上抽取的结果。
抽象类和接口的区别:
1)抽象类只能被继承,而且只能单继承。
接口需要被实现,而且可以多实现。
2)抽象类中可以定义非抽象方法,子类可以直接继承使用。
接口中都是抽象方法,需要子类去实现。
3)抽象类使用的是 is a 关系。
接口使用的 like a 关系。
4)抽象类的成员修饰符可以自定义。
接口中的成员修饰符是固定的。全都是public的。
4.5 多态
函数本身就具备多态性,某一种事物有不同的具体的体现。
1) 多态的体现
父类的引用指向了自己的子类对象。
父类的引用也可以接收自己的子类对象
利用代码来体现多态:
abstract class Animal
{
public abstract void eat();
}
class cat extends Animal
{
public void eat()
{
System.out.println("吃鱼");
}
public void catchMouse()
{
System.out.println("抓老鼠");
}
}
class Dog extends Animal
{
public void eat()
{
System.out.println("吃骨头");
}
public void kenJia()
{
System.out.println("看家");
}
}
class Pig extends Animal
{
public void eat()
{
System.out.println("饲料");
}
public void gongDi()
{
System.out.println("拱地");
}
}
class DuoTaiDemo
{
public static void main(String[] args)
{
function(new Cat()); //Cat(),Dog(),Pig()为Animal()的子类,<span style="font-family: Arial, Helvetica, sans-serif;">所以以此为对象可以调用function函数</span>
function(new Dog());
function(new Pig()); //运行时引用的是子类中的eat(),子类将父类覆盖了
}
public static void function(Animal a) //Animal a = new Cat();
{
a.eat();
}
}
<p>输出结果为吃鱼吃骨头 饲料,利用多态的特性简化了代码</p>
由此得到:
2) 多态的前提
必须是类与类之间有关系,要么继承,要么实现
通常还有一个前提:存在覆盖
比如:在function代码中
a.catchMouse(); 将会报错
3) 多态的好处
多态的出现打打的提供程序的扩展性
4) 多态的弊端
提高了扩展性,但是只能使用父类的引用访问父类中的成员。
5)转型
class DuoTaiDemo
{
public static void main(String[] args)
{
Animal a = new Cat(); //类型提升,向上转型,将子类转为父类型
Cat c = (Cat)a; //如果想要调用子类中的特殊方法
c.catchMouse; //就可以强制将父类的引用转成子类类型,向下转型
}
}
能够转换的是父类应用指向了自己的子类对象时,该应用可以被提升,也可以被强制转换
Animal a = new Animal();
Cat c = (Cat) a; //不可以这样转型
多态自始至终都是子类对象在做着变化
如果想用子类对象的特有方法,如何判断对象是哪个具体的子类类型呢?
可以可以通过一个关键字 instanceof; //判断对象是否实现了指定的接口或继承了指定的类
Cat instanceof a // 判断Cat是不是at类型
多态在子父类中的成员上的体现的特点:
1)成员变量:在多态中,子父类成员变量同名。
在编译时期:参考的是引用型变量所属的类中是否有调用的成员。(编译时不产生对象,只检查语法错误)
运行时期:也是参考引用型变量所属的类中是否有调用的成员。
简单一句话:无论编译和运行,成员变量参考的都是引用变量所属的类中的成员变量。
再说的更容易记忆一些:成员变量 --- 编译运行都看 = 左边。
2)成员函数。
编译时期:参考引用型变量所属的类中是否有调用的方法。
运行事情:参考的是对象所属的类中是否有调用的方法。
为什么是这样的呢?因为在子父类中,对于一模一样的成员函数,有一个特性:覆盖。
简单一句:成员函数,编译看引用型变量所属的类,运行看对象所属的类。
更简单:成员函数 --- 编译看 = 左边,运行看 = 右边。
3)静态函数。
编译时期:参考的是引用型变量所属的类中是否有调用的成员。
运行时期:也是参考引用型变量所属的类中是否有调用的成员。
为什么是这样的呢?因为静态方法,其实不所属于对象,而是所属于该方法所在的类。
调用静态的方法引用是哪个类的引用调用的就是哪个类中的静态方法。
简单说:静态函数 --- 编译运行都看 = 左边。
4.6 object类
类object是类层次结构的根类,是所有对象的直接或者间接父类。Java认为所有的对象都具备一些基本的共性内容,这些内容可以不断的向上抽取,最终就抽取到了一个最顶层的类中的,该类中定义的肯定是所有对象都具备的功能
几个常用的方法:
1)boolean equals(Object obj):用于比较两个对象是否相等,其实内部比较的就是两个对象地址。
而根据对象的属性不同,判断对象是否相同的具体内容也不一样。所以在定义类时,一般都会复写equals方法,建立本类特有的判断对象是否相同的依据。
publicboolean equals(Object obj)
{
if(!(obj instanceof Person))
returnfalse;
Person p = (Person)obj;
return this.age == p.age;
}
2,String toString():将对象变成字符串;默认返回的格式:类名@哈希值 = getClass().getName() + '@' + Integer.toHexString(hashCode())
为了对象对应的字符串内容有意义,可以通过复写,建立该类对象自己特有的字符串表现形式。
publicString toString()
{
return "person : "+age;
}
3,Class getClass():获取任意对象运行时的所属字节码文件对象。
4,int hashCode():返回该对象的哈希码值。支持此方法是为了提高哈希表的性能。
4.7 内部类
内部类:如果A类需要直接访问B类中的成员,而B类又需要建立A类的对象。这时,为了方便设计和访问,直接将A类定义在B类中。就可以了。A类就称为内部类。
内部类的访问规则:
1) 内部类可以直接访问外部类中的成员,包括私有
之所以可以直接访问外部类中的成员,是因为内部类中持有了一个外部类的引用
格式: 外部类名.this.成员
2) 外部类要访问内部类,必须建立内部类对象
访问格式:
1)当内部类定义在外部类的成员位置上,而且非私有,可以在外部其他类中直接建立内部类对象
外部类名.内部类名 变量名 = 外部类对象.内部类对象;
Outer.Innerin = new Outer().new Inner();
2)当内部类在成员位置上,就可以被成员修饰符所修饰
比如 private : 将内部类在外部类中进行封装
Static :内部类就具备static的特性,当内部类被static修饰后,只能直接访问外部类中的static成员,出现了访问局限
在外部其他类中,如何直接访问static内部类的非静态成员:
new Outer.Inner().function();
在外部其他类中,如何直接访问static内部类的静态成员:
Outer.Inner.function();
注意:当内部类中定义了静态成员,该内部类必须是static的。
当外部类中的静态方法访问内部类时,内部类也必须是static的。
3)内部类定义在局部时:
① 不可以被成员修饰符修饰。
② 可以直接访问外部类中的成员,因为还持有外部类中的引用。但是不可以访问它所在的局部中的变量,只能访问被final修饰的局部变量
匿名内部类:
1) 匿名内部类其实就是内部类的简写格式
2) 定义匿名内部类的前提:
内部类必须是继承一个类或者实现接口
3) 匿名内部类的格式: new父类或者接口(){定义子类的内容}
4) 其实匿名内部类就是一个匿名子类对象,可以理解为带内容的对象
5) 匿名内部类中定义的方法最好不要超过3个
匿名内部类实例:
interface Inter
{
void method();
}
class Test
{
static Inter function() //function方法得到的是一个Inter类型的对象
{
return new Inter()
{
public void method()
{
System.out.println("method run");
}
};
}
}
class InnerClassTest
{
public static void main(String[] args)
{
Test.function().method(); //得到对象后可以调用其内部的方法。
}
}