1、类、超类和子类
2、Object:所有类的超类
3、泛型数组列表
4、枚举类
5、接口
(1)、定义子类
可以如下继承Employee类来定义Manager类,这里使用关键字extends来表示继承。
public class Manager extends Employee
{
added methods and fields
}
注:Java与c++定义继承的方式十分相似。Java使用关键字extends代替了c++中的冒号。在Java中,所有的继承都是公共继承,而没有c++中的私有继承和保护继承。
在Manager类中,增加了一个用于存储奖金信息的字段,以及一个用于设置这个字段的新的方法:
public class Manager extends Employee
{
private double bonus;
...
public void setBonus(double bonus)
{
this.bonus=bonus;
}
}
这里定义的方法和字段并没有什么特别之处。如果有一个Manager对象,就可以使用setBonus方法。
Manager boss=...; boss.steBonus(50000);
当然,由于setBonus方法不是在Employee类中定义的,所以属于Employee类的对象不能使用它。
然而,尽管在Manager类中没有显示地定义getName和getHireDay等方法,但是可以对Manager对象使用这些方法,这是因为Manager类自动地继承了超类Employee中的这些方法。
类似地,从超类中还继承了name、salary、和hireDay这三个字段。这样一来,每个Manager对象就包含了四个字段:name、salary、hireDay和bonus。
(2)覆盖方法
超类中的有些方法对子类Manager并不一定适用,为此,需要提供一个新的方法来覆盖超类中的方法。我们希望调用超类Employee中的getSalary方法,而不是当前类的这个方法。为此,可以使用特殊的关键字super解决这个问题。
super.getSalary();
这个语句调用的是Employee中的getSalary方法。下面是Manager类中的getSalary方法的正确版本:
public double getSalary()
{
double baseSalary=super.getSalary();
return baseSalary+bonus;
}
(3)子类构造器
public Manager(String name,double salary,int year,int month,int day)
{
super(name,salary,year,month,day);
bonus=0;
}
由于Manager类中的构造器不能访问Employee类中的私有字段,所以必须通过一个构造器来初始化这些私有字段。可以利用特殊的super语法调用这个构造器。使用super构造器的语句必须是子类构造器的第一条语句。
如果子类的构造器没有显示地调用超类的构造器,将自动的调用超类的无参构造器,如果超类没有无参构造器并且在子类的构造器中又没有显示地调用超类的其他构造器,Java编译器会报告一个错误。
(4)多态
一个对象变量可以指示多种实际类型的现象称为多态,在运行时能够自动选择适当的方法称为动态绑定。
在Java程序设计语言中,对象变量时多态的。一个Employee类型的变量既可以引用一个Employee类型的对象,也可以引用Employee类的任何一个子类的对象。
(5)final关键字
有时候我们可能希望阻止人们利用某个类定义子类,不允许扩展的类被称为final类。如果在定义类的时候使用了final修饰符就表明这个类时final类。例如,假设人们派生Executive类的子类,就可以在声明这个类的时候使用final修饰符。声明格式如下所示:
public final class Executive extends Manager
{
...
}
类中的某个特定方法也可以被声明为final,如果这样做,子类就不能覆盖这个方法(final类中的所有方法自动地称为final方法)。例如
public class Employee
{
...
public final String getName()
{
return name;
}
...
}
(6)强制类型转换
有时候需要将浮点数转换成整数一样,需要将某个类的对象引用转换成另外一个类的对象引用。要完成对象引用的强制类型转换,转化语法与数值表达式的强制类型转换类似,只需要用一对圆括号将目标类名括起来,并放置在需要转换的对象引用之前就可以了。
@只能在继承层次内进行强制类型转换
@在将超类强制转换成子类之前,应该使用instanceof进行检查。
(7)抽象类
为了提高程序的清晰度,包含一个或多个抽象方法的类本身必须被声明为抽象的。
public abstract class Person
{
...
public abstract String getDescription();
}
除了抽象方法外,抽象类还可以包含字段和具体方法,例如,Person类还保存着一个人的姓名,另有一个返回姓名的具体方法。
public abstract class Person
{
private String name;
public Person(String name)
{
this.name=name;
}
public abstract String getDescription();
public String getName()
{
return name;
}
}
即使不含抽象方法,也可以将类声明为抽象类。
抽象类不能实例化,也就是说,如果将一个类声明为abstract,就不能创建这个类的对象,但是可以创建一个具体子类的对象。
需要注意,可以定义一个抽象类 的对象变量,但是这样一个变量只能引用非抽象子类的对象。
下面定义一个扩展抽象类Person的具体子类Student:
public class Student extends Person
{
private String major;
public Student(String name,String major)
{
super(name);
this.major=major;
}
public String getDescription()
{
return"a student majoring in"+major;
}
}
Student类定义了getDescription方法,因此,在Student类中的全部方法都是具体的,这个类不再是抽象类。
对Java中的四个访问控制修饰符做个小结:
1、仅对本类可见——private
2、对外部完全可见——public
3、对本包 和所有子类可见——protected
4、对本包可见——默认(很遗憾哈哈哈哈哈哈哈哈哈),不需要修饰符。
二、Object:所有类的超类
Object类是Java中所有类的始祖,在Java中每个类都扩展了Object。
(1)、Object类型的变量
可以使用Object类型的变量引用任何类型的对象:
Object obj=new Employee("Harry Hacker",35000);
当然,Object类型的变量只能用于作为各种值的一个泛型容器。要相对其中的内容进行具体的操作,还需要清楚对象的原始类型,并进行相应的强制类型转换:
Employee e=(Employee) obj;
(2)equals方法
Object类中的equals方法用于检测一个对象是否等于另外一个对象。Object类中实现equals方法将确定两个对象引用是否相等。如下为equals方法实现的机制:
public class Employee
{
...
public boolean equals(Object otherObject)
{
//a quick test to see if the objects are identical
if(this==otherObject)
return true;
//must return false if the explicit parameter is null
if(otherObject==null)
return false;
//if the classes do not match,they cannot be equal
if(getClass()!=otherObject.getClass())
return false;
//now we know otherObject is non-null Employee
Employee other=(Employee) otherObject;
//tese whether the fields have identical values
return name.equals(other.name)
&&salary==other.salary
&&hireDay.equals(other.hireDay);
}
}
在子类中定义equals方法时,首先调用超类的equals。如果检测失败,对象就不可能相等。如果超类中的字段都相等,就需要比较子类中的实例字段。
public class Manager extends Employee
{
...
public boolean equals(Object otherObject)
{
if(!super.equals(otherObject))
return false;
//super.equals checked that this and otherObject belong to the same class
Manager other=(Manager)otherObject;
return bonus == other.bonus;
}
}
(3)toString方法
在Object类中还有一个重要的方法就是toString方法,它会返回表示对象值的一个字符出串。
绝大多数的toString方法都遵循这样的格式:类的名字,随后是一对方括号括起来的字段值。下面是Employee类中toString方法的实现:
public String toString()
{
return "Employee[name="+name+",salary="+salary+",hireDay="+hireDay+"]";
}
最好通过调用getClass().getName()过的类名的字符串,而不需要将类名硬编码到toString方法中
public String toString()
{
return getClass().getName()
"Employee[name="+name+",salary="+salary+",hireDay="+hireDay+"]";
}
这样的方法也可以又子类调用。
当然,设计子类的程序员应该定义自己的toString方法,并加入子类的字段。如果超类使用了getClass().getName(),那么子类只要调用super.toString()就可以了。例如,下面是Manager类中的toString方法:
public class Manager extends Employee
{
...
public String toString()
{
return super.toString()+"[bonus"+bonus+"]";
}
}
三、泛型数组列表
(1)、声明
声明和构造一个保存Employee对象的数组列表:
Arrayliststaff=new ArrayList ();
在Java10中最好使用var关键字以避免重复写类名:
var staff=new ArrayList();
如果没有使用var关键字,可以省去右边的类型参数:
ArrayListstaff=new ArrayList<>();
这称为“菱形”语法。
如果使用var声明ArrayList,就不要使用菱形语法。以下声明:
var elements=new ArrayList<>();
会生成一个ArrayList
可以使用add方法将元素添加到数组列表中,例如:
staff.add(new Employee("Harry Hacker",...));
staff.add(new Employee("Tony Tester",...));
(2)访问数组列表元素
不能使用我们喜爱的 [ ] 语法格式访问或者改变数组的元素,而要使用get和set方法。
例如要设置第i个元素,可以使用:
staff.set(i,harry);
它等价于对数组a的元素赋值(与数组一样,下表值从0开始):
a[i]=harry;
要得到一个数组列表的元素,可以使用:
Employee e=staff.get(i);
这等价于:
Employee e = a[i];
下面这个技巧可以一举两得,既可以灵活的扩展数组,又可以方便地访问数组元素:
var list = new ArrayList(); while(...) { x=...; list.add(x); }
四、枚举类
下面是一个示例:
public enum Size
{
SMALL("S"),MEDIUM("M"),LARGE("L"),EXTRA-LARGE("XL");
private String abbreviation;
private Size(String abbreviation)
{
this.abbreviation=abbreviation;
}
public String getAbbreviation()
{
return abbreviation;
}
}
枚举类的构造器总是私有的,可以想前例中一样省略private修饰符。如果声明一个enum构造器为public或者protected会出现语法错误。
所有的枚举类型都是Enum类的子类,他们继承了这个类的许多方法。其中最有用的一个是toString,这个方法会返回枚举常量名。例如,Size.SMALL.toString()将返回字符串“SMALL”。
toString的逆方法是静态方法valueOf,例如以下语句:
Size s=Enum.value(Size.class,"SMALL");
将s设置成Size.SMALL。
每个枚举类型都有一个静态的values方法,它将返回一个包含全部枚举值的数组。例如以下调用:
Size[]values=Size.values();
返回包含元素Size.SMALL、Size.MEDIUM、Size.LARGE和Size.EXTERA-LARGE的数组。
ordinal方法返回enum声明中枚举常量的位置,位置从0开始计数。
五、接口
(1)接口的概念
在Java程序设计语言中,接口不是类,而是对希望符合这个接口的类的一组需求。
下面给出一个具体的示例,Arrays类中的sort方法承诺可以对对象数组进行排序,但要求满足下面这个条件:对象所属的类必须实现Comparable接口。
下面是Comparable接口的代码:
public interface Comparable
{
int compareTo(Object other);
}
为了让类实现一个接口,通常需要完成下面两个步骤:
1、将类声明为实现给定的接口。
2、对接口中的所有方法提供定义。
要将类声明为实现某个接口需要使用关键字implements;
(2)接口的属性
接口不是类,具体来说,不能用new运算符实例化一个接口,不过可以声明接口的变量,接口的变量必须引用实现了这个接口的类对象。
虽然在接口不能包含实例字段,但是可以包含常量。与接口中所有的方法都自动被设置为public一样,接口中的字段总是public static final。
若想实现两个接口可以使用逗号将要实现的各个接口分隔开。
class Employee implements Cloneanle,Copmarable
(3)、接口与抽象类
使用抽象类表示通用属性存在一个严重的问题,每个类只能扩展一个类,假设Employee类已经扩展了另一个类,它就不能再扩展第二个类了,但是每个类可以实现多个接口。
实际上,接口可以提供多继承的大多数好处,同时还能避免多重继承的复杂性和低效性。
(4)、默认方法
可以为接口方法提供一个默认实现。必须用default修饰符标记这样一个方法。
public interface Comparable{ default int compareTo(T other) { return 0; } //by default, all elements are the same }
(5)、解决默认方法冲突
其规则如下:
1、超类优先。如果超类提供了一个具体方法,同名而具有相同参数类型的默认方法会被忽略。
2、接口冲突。如果一个接口提供了一个默认方法,另一个接口提供了一个同名而且参数类型(不论是否是默认参数)相同的方法,必须覆盖这个方法解决冲突。
如果一个类扩展了一个超类同时实现了一个接口,并从超类和接口继承了相同的方法,只会考虑超类的方法,接口所有默认方法都会被忽略。



