继承
1 类、超类及子类
1.1 定义子类
在java中,使用关键字extends
进行继承,具体实现方式如下,这里Manager
表示子类,Employee
表示超类,下面所示关系是类Manager
继承于类Employee
。
pubic Manager extends Employee
{
}
通过这种继承的方式,子类可以继承很多超类中允许继承的方法和变量,因此子类定义的对象能够直接使用这些方法和变量。
1.2 覆盖方法
当超类中的一些方法不能够满足子类的需求时,可以定义一个一样名字的方法,但是方法中实现内容有所不同,
1.3 子类构造器
同样是一个类,作为子类也是需要一个构造器进行构造对象,不过有一点需要明白的是,每次用子类创建对象的时,均会执行子类构造器之前先执行超类构造器,如此继续往上调用一直到Object
的构造器,等Object
构造完成之后,执行超类构造器,最后执行子类构造器。举个例子,如下
class Animal
{
public Animal()
{
System.out.println("Making an animal");
}
}
class Dog extends Animal
{
public Dog()
{
System.out.println("Making a Dog");
}
}
public class Test
{
public static void main(String args[])
{
Dog a=new Dog();
}
}
在上述例子中,当创建一个Dog
类的对象时,首先调用Dog
类的构造函数Dog()
,并将其压入栈,接着Dog
会调调用超类的构造函数,将超类的构造函数Animal()
压入栈,最后会调用Object
类的构造函数,并将其压入栈中;接下来就会依次按照先进后出的顺序将这些构造函数出栈,出一次栈就调用相应出栈函数,因此,会先调用超类的构造函数,其次再调用子类的构造函数。
在上面这段中说会调用超类的构造函数,这是怎么回事呢?子类的构造函数在执行的时候,首先均会调用超类的构造函数,具体可以分为两种:
- 第一种,我们可以且必须在子类构造函数中的第一条语句,通过调用
super()
函数的形式调用超类的构造函数(可以是无参构造也可以是有参构造) - 第二种,如果不通过上述方式
显式
调用超类构造函数,系统会自动调用超类的无参构造函数。当然这种情况下一定需要超类有无参构造函数,如果没有无参构造函数系统就会报错
1.4 继承层次
在java中,支持一个超类具有多个子类,但是不支持一个子类继承于多个超类(这一点和C++有所不同)
1.5 多态
在java中,任何对象变量均是多态的,简单来说,就是一个超类的变量既可以引用超类对象
也可以引用子类对象
,相反,一个子类的变量不可以引用超类对象;如下述程序中的超类Animal
与子类Dog
,当超类定义的变量a
引用子类对象时,这时候变量a
是一个超类变量,所以会导致不能调用子类的特有函数bark()
,如果想使用必须将变量a
强制转换为子类;但是呢,变量a
可以调用子类中覆盖超类中的函数,如下面程序中的函数make()
,如果执行a.make()
,则会自动调用子类中的make()
函数,这正是多态神奇的地方!
- 总结以上所说,多态的特点就是超类变量可以引用子类对象,但是在使用超类变量调用方法时,只能调用子类的对超类覆盖的方法,不能调用子类特有的方法
- 如果要想一个超类变量能够使用子类特有的方法,可以将其强制转换为子类类型,如下面程序所示,将
c[0]
由Animal
类型强制转换为Dog
之后(Dog e=(Dog)c[0]
),就可以使用Dog
类中的bark()
方法了
class Animal
{
public Animal()
{
System.out.println("Making an animal");
}
public void make()
{
System.out.println("Animal:make()");
}
public void eat()
{
System.out.println("Animal:eat()")
}
}
class Dog extends Animal
{
public Dog()
{
System.out.println("Making a Dog");
}
public void make()
{
System.out.println("Dog:make()");
}
public void bark()
{
System.out.println("Dog:bark()");
}
public void eat()
{
System.out.println("Dog:eat()")
}
}
class Cat extends Animal
{
public Cat()
{
System.out.println("Making a cat");
}
public void eat()
{
System.out.println("Cat:eat()");
}
}
public class Test
{
public static void main(String args[])
{
Animal a=new Animal();//超类变量引用超类对象
Animal b=new Dog();//超类变量引用子类对象
a=b;//超类变量引用子类对象
Animal[] c=new Animal[2];
c[0]=new Dog();
c[1]=new Cat();
c[0].eat();//执行Dog类中的eat()函数
c[0].bark();//不能执行,因为c[0]还只是一个Animal变量
c[1].eat();//执行Cat类中的eat()函数
Dog e=(Dog)c[0];
e.bark();//将c[0]类型转换为Dog后就可以使用其特有的方法了
}
}
1.6 理解方法调用
当如果出现类c
继承于类b
,类b
继承于类a
时,这时候具体的方法调用情况是如何的呢?举例说明。
class Animal
{
void makenoise()
{
System.out.println("Animal:makenoise()");
}
void eat()
{
System.out.println("Animal:eat()");
}
void sleep()
{
System.out.println("Animal:sleep()");
}
void roam()
{
System.out.println("Animal:roam()");
}
}
class Canine extends Animal
{
void roam()
{
System.out.println("Canine:roam()");
}
}
class Wolf extends Canine
{
void makenoise()
{
System.out.println("Wolf:makenoise()");
}
void eat()
{
System.out.println("Wolf:eat()");
}
}
public class Test
{
public static void main(String args[])
{
Wolf a=new Wolf();
a.makenoise();//调用Wolf类中的makenoise()函数
a.roam();//调用Canine类中的roam()函数
a.eat();//调用Wolf类中的eat()函数
a.sleep();//调用Animal类中的sleep()函数
}
}
上述例子表明,当出现多阶继承时,由于子类会继承来自超类的一些方法,所以会从继承级别的最低阶开始往最高阶寻找调用的方法,直到寻找到为止。
1.7 阻止继承
当我们想要使的自己写的类不允许被继承时,可以使用final
对类进行声明,一旦类被声明了final
,就表示该类不允许衍生出任何子类。如下所示。
public final class Excutive extends Manager
{
}
//声明final类的方式如上所示
此外,也可以对类中的某个具体方法声明为final
,这样的方法具有什么含义呢?其含义就是,子类中不能覆盖该方法,并且需要说的时final
类中的任何方法均自动的成为final
方法,但是需要注意的是变量不会成为final
。
通过这种方法,可以知道,任何final
类的引用一定是该final
类对象,不可能是其子类对象。如java中的String
类,其也是final
类,因此String
类的引用一定是String
类本身的对象。
1.8 抽象类与方法
- 抽象类是一种
奇怪的类
,为什么这么说呢?因为其不能被初始化,没有这种类的对象可以产生,简单来说,就是不能new
出来。但是,还是可以将这种类作为引用类型,来引用一些子类对象。抽象类的定义如下。
abstract class Canine extends Animal
{
}
class Dog extends Canine
{
}
public static void main(String[] args)
{
Canine a=new Canine();//编译错误,因为等号右边不能创建抽象类的实例对象
Canine b=new Dog();//编译成功,虽然不能创建抽象类的对象,但是可以有抽象类的引用
}
含有大于一个抽象方法的类必须被标记为抽象的,抽象类中可以包含抽象方法和非抽象方法(抽象类中也可以不含有抽象方法);通过上面可以发现,其实抽象类除了继承之外没有其他任何用处。
- 除了抽象类之外,还有抽象方法,抽象方法是没有实体的!如下所示是抽象类的定义方式。另外需要注意的是一旦声明了一个抽象方法,则必须同时将类标记为抽象类(不能在非抽象类中定义抽象方法)
public abstract void eat();
1.9 受保护访问
为了保护变量与方法,最好是将类中的方法或者变量声明为private
,但是这样不利于在继承的时候使得子类利用超类的变量或者方法,所以为了既能使其他类不能使用超类中的一些方法或者变量,且又能使得在子类中能使用一些方法或者变量,可以将这些方法或者变量设置为protected
类型,设置为protected
类型的变量或者方法属于受保护的,其只能在子类中被访问,其他类不能使用。
2 Object:所有类的超类
在java中,Object
类是所有类的超类,也就是说所有的类都是扩展而来的。在我们定义一个类的时候,如果没有没有明确指出其超类,那么这个类的超类默认就是Object
类,但是通常会省略掉。因此可以使用Object
类的变量引用任何类的变量。
public class Cat
{
}
//相当于
public class extends Object
{
}
其次,在java中只有基本类型
不是对象,如数值、字符以及布尔类型的值这些均不是对象。
2.1 equals方法
在java中,Object
类中提供了比较两个对象是否相等的方法,即equals()
,该方法在检测时,如果两个对象具有相同的引用,则认为是相等的。
2.2 hashCode方法
hashCode
称为散列码,其是由一个对象导出的整型值,并且这个值是对象的存储地址。
2.3 toString方法
在Object
类中,toString()
可以有效返回对象值的字符串。
3 泛型数组列表
为了能够动态更改数组大小的问题,在java中定义了一个称为ArrayList
的类,这种类能够实现动态的添加于删除数组中的元素。ArrayList
类是一个采用类型参数的泛型类,使用方式如下。
ArrayList<Employee> staff=new ArrayList<>();
这种泛型类中具有很多操作方法,用于添加元素,删除元素等等。
-
add(int index,E obj)
,在指定位置添加元素 -
size()
,返回数组中的元素个数 -
ensureCapacity()
,用于确定数组的大小 - 还可以和定义一般数组一样,定义泛型数组,如
ArrayList<Employee> staff=new ArrayList<>(50)
,但是这里和一般的定义数组不同,这里定义的大小表明其具有存储50个数据的能力,在这些空间使用完后,还会继续分配空间使用;相反,一般的定义数组不同,定义多大的空间,只能使用这么大的空间,一旦超过了就不行 -
set(int index,E obj)
,用于设置指定位置的元素值,将覆盖原来的元素 -
E get(int index)
,将返回指定位置的元素值 -
E remove(int index)
,删除指定位置上的元素,后面的元素会向前移动一个位置
4 对象包装器与自动装箱
有时候我们想要将基本数据类型当做对象进行处理,因此为了实现这种转换。在java中,所有的基本数据类型均有个与之相对应的包装类
,这些类也称为包装器
。如下所示是基本数据类型的包装器
Boolean
Character
Byte
Short
Integer
Long
Float
Double
如下述所示,包装与解开包装
Integer data=new Integer(99);//将改值99进行包装
int d=data.intValue();//解开包装,获得d等于99
- 在自动包装出现之前,基本数据类型与包装器是是严格区分开的,如下面所示
ArrayList array=new ArrayList();
array.add(new Integer(999));//必须将999转换为Object类型
Integer one=(Integer)array.get(0);//取出的时候必须将其转换为Integer
int oneint=one.intValue();//将Integer转换为int
有了自动包装之后,就没有必要按照上面严格讲数据与类型区分开,如下所示
ArrayList<Integer> listnum=new ArrayList<Integer>();
listnum.add(1999);//int数据自动转换为Integer
int listNumber=listnum.get(0);//Integer自动转换为int
- 在上面程序中,
listnum.add(1999)
这条语句将自动转换为listnum.add(Integer.valueOf(1999))
,这就叫做自动装箱
- 在上面程序中,
listnum.get(0)
这条语句将自动转换为listnum.get(0).intValue()
,这就叫做自动拆箱
此外还有一些包装类中还有一些静态方法,如parseInt()
、parseDouble()
等等,这些方法可以将字符串转换为int
或者double
类型,如下面所示
String str="9";
int strNum=Integer.parseInt(str);
String str1="123.45";
double strNum1=Double.parseDouble(str1);
反过来,我们也可以将int
或者double
转换为字符串类型,如下面所示
double dou0=123.45;
String str0=""+dou0;//第一种
String str1=Double.toString(dou0);//第二种使用Double类中的静态成员方法