多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。此处的多个类称为子类(此类,派生类,拓展类),单独的这个类称为父类(基类 ,超类)。可以理解为:“子类 is a 父类”。
类继承语法规则:
class Subclass extends SuperClass{ };
以下几点需要注意:
在Java类中使用super来调用父类中的指定操作:
构造方法用于构建一个类的实例。不同于属性和普通方法,父类的构造方法不会被子类继承。它们只能使用关键字 super 从子类的构造方法中调用。
调用父类构造方法的语法是:
super()或者super(parameters);
public class Person {
private String name;
private int age;
private Date birthDate;
public Person(String name, int age, Date d) {
this.name = name;
this.age = age;
this.birthDate = d; }
public Person(String name, int age) {
this(name, age, null);
}
public Person(String name, Date d) {
this(name, 30, d);
}
public Person(String name) {
this(name, 30);
}
}
public class Student extends Person {
private String school;
public Student(String name, int age, String s) {
super(name, age);
school = s; }
public Student(String name, String s) {
super(name);
school = s; }
// 编译出错: no super(),系统无法调用父类无参数的构造器。
//There is no default constructor available in 'chapter01.Person'
public Student(String s) {
school = s; }
}
在任何情况下,构造一个类的实例时,将会调用沿着继承链的所有父类的构造方法。当构造一个子类的对象时,子类构造方法会在完成自己的任务之前,首先调用它的父类的构造方法。如果父类继承自其他类,那么父类构造方法又会在完成自己的任务之前,调用它自己的父类的构造方法。这个过程持续到沿着这个继承体系结构的最后一个构造方法被调用为止。这就是构造方法链(constructor chaining)。
public class Faculty extends Employee {
public static void main(String[] args) {
Faculty faculty = new Faculty();
}
public Faculty() {
System.out.println("(4) Performs Faculty's tasks");
}
}
class Employee extends Person {
public Employee() {
this("(2)Invoke Employee's overloaded constructor");
System.out.println("(3)Performs Employee's tasks ");
}
public Employee(String s) {
System.out.println(s);
}
}
class Person {
public Person() {
System.out.println("(1) Performs Person's tasks");
}
}
结果:
在第3 行,new Faculty() 调用Faculty 的无参构造方法。由于 Faculty 是 Employee 的子类,所以,在Faculty 构造方法中的所有语句执行之前,先调用 Employee 的无参构造方法。Employee 的无参构造方法调用Employee 的第二个构造方法(第13 行)。由于 Employee 是 Person 的子类,所以,在 Employee 的第二个构造方法中所有语句执行之前,先调用 Person 的无参构造方法。
super.方法名(参数);
子类从父类中继承方法。有时,子类需要修改父类中定义的方法的实现,这称作方法重
写(method overriding),:要重写一个方法,需要在子类中使用和父类一样的签名以及一样的返回值类型来对该方法进行定义。
注意以下几点:
这个再次详细说明一下
public class StaticExtends {
public static void main(String[] args) {
//声明为Father类,然后son1静态方法和Father类绑定
Father son = new Son();
son.method();
son.staticMethod();
Son son2 = new Son();
son2.method();
son2.staticMethod();
}
}
class Father {
void method() {
System.out.println("父类方法");
}
static void staticMethod() {
System.out.println("父类静态方法");
}
}
class Son extends Father {
@Override
void method() {
System.out.println("子类方法");
}
static void staticMethod() {
System.out.println("子类静态方法");
}
}
输出结果:
在子类中重写父类的static方法,是不会报错的,编译也可以通过,但是在通过一个声明为父类,实际类型为子类的引用变量调用该方法时,发现被调用的仍是父类中原本以为会被覆盖的方法,不具有“多态”特性。所以呢,父类的static方法是不会被重写的。
Java 中的所有类都继承自 java.lang.Object 类,如果在定义一个类时没有指定继承性,那么这个类的父类就被默认为是 Object。
Object 类中toString()方法的默认实现是:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
调用一个对象的 toString() 会返回一个描述该对象的字符串。默认情况下,它*返回一个由该对象所属的类名、at 符号(@)以及该对象十六进制形式的内存地址组成的字符串。*这个信息不是很有用,所以重写。
s1=“hello”;
System.out.println(s1);//相当于System.out.println(s1.toString());
int a=10;
System.out.println(“a=”+a);
Object 类中 equals 方法的默认实现是:
public boolean equals(Object obj) {
return (this obj);
}
说明:Object类中定义的equals()和==的作用是相同的:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体。
tips:==和equals的区别:
在Java中声明类、变量和方法时,可使用关键字final来修饰,表示“最终的”。
- String类、System类、StringBuffer类
public final class Test {
public static int totalNumber = 5;
public final int ID;
public Test() {
ID = ++totalNumber; // 可在构造器中给final修饰的“变量”赋值
}
public static void main(String[] args) {
Test t = new Test();
System.out.println(t.ID);
final int I = 10;
final int J; J = 20;
J = 30; // 非法
}
}
首先呢,我们知道继承关系使一个子类继承父类的特征,并且附加一些新特征。子类是它的父类的特殊化,每个子类的实例都是其父类的实例,但是反过来就不成立。例如:每个圆都是一个几何对象,但并非每个几何对象都是圆。因此,总可以将子类的实例传给需要父类型的参数。使用父类对象的地方都可以使用子类的对象。这就是通常所说的多态。简单来说,多态意味着父类型的变量可以引用子类型的对象。
我们都知道方法可以在父类中定义而在子类中重写。(方法可以在沿着继承链的多个类中实现。JVM 决定运行时调用哪个方法。)那么
Object o = new SonObject();
System.out.println(o.toSting);
这里的 o 调用哪个 tostring() 呢?
我们首先介绍两个术语:声明类型和实际类型。**一个变量必须被声明为某种类型。变量的这个类型称为它的声明类型(declared type)。**这里,o 的声明类型是 Object。一个引用类型变量可以是一个 null 值或者是一个对声明类型实例的引用。实例可以使用声明类型或它的子类型的构造方法创建。变量的实际类型(actual type) 是被变量引用的对象的实际类。这里,o 的实际类型是SonObject, 因为 o 指向使用 new SonObject() 创建的对象。o 调用哪个toString() 方法由 o 的实际类型决定。这称为动态绑定(dynamic binding)。
也就是多态情况下,编译时,看左边;运行时,看右边。
“看左边”:看的是父类的引用(父类中不具备子类特有的方法)
“看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)
动态绑定工作机制如下:假设对象 o 是类 Cl, C2, … ,Cn-1, Cn 的实例,其中 C1是 C2的子类,C2 是 C3 的子类,… ,Cn-1是 Cn 的子类。也就是说,Cn 是最通用的类,C1是最特殊的类。在 Java 中,Cn 是 Object 类。如果对象 o 调用一个方法 p, 那么JVM 会依次在类 Cl,C2, … ,Cn-1,Cri 中查找方法 p 的实现,直到找到为止。一旦找到一个实现,就停止査找,然后调用这个首先找到的实现。
看以下代码:
public class DynamicBindDemo {
public static void main(String[] args) {
m(new GraduateStudent());
m(new Student());
m(new Person());
m(new Object());
}
public static void m(Object x) {
System.out.println(x.toString());
}
}
class GraduateStudent extends Student {
}
class Student extends Person {
@Override
public String toString() {
return "Student";
}
}
class Person {
@Override
public String toString() {
return "Person";
}
}
对象的引用可以类型转换为对另外一种对象的引用,这称为对象转换。
Object o = new Student();
m(o);
Student 的实例也是 Object 的实例,所以,语句 Object o = new StudentO 是合法的,它称为隐式转换(implicit casting)。
但是Student b = o;将会发生编译错误,原因是 Student 对象总是 Object 的实例,但是,Object 对象不一定是 Student 的实例。即使可以看到 0实际上是一个 Student 的对象,但是编译器还没有聪明到知道这一点。为了告诉编译器o就是一个 Student 对象,就要使用显式转换( explicit casting)。
Student b = (Student)o;// Explicit casting
总是可以将一个子类的实例转换为一个父类的变量,称为向上转换(upcasting),因为子类的实例永远是它的父类的实例。当把一个父类的实例转换为它的子类变量(称为向下转换(downcasting))时,必须使用转换记号 “(子类名)” 进行显式转换,向编译器表明你的意图。为使转换成功,必须确保要转换的对象是子类的一个实例。如果父类对象不是子类的一个实例,就会出现一个运行异常 ClassCastException。
在尝试转换之前确保该对象是另一个对象的实例,可以利用运算符instanceof 来实现的。
Objecto = new Circle();
if (o instanceof Circle){
Circle c = ((Circle)o;
System.out.println("The circle diameter is + o.getDiameter());
变量 myObject 被声明为 Object。**声明类型
决定了在编译时匹配哪个方法。**使用 myObject.getDiameter()会引起一个编译错误,因为Object 类没有 getDiameter 方法。编译器无法找到和 myObject.getDiameter()匹配的方法。所以,有必要将 myObject 转换成 Circle 类型,来告诉编译器 myObject 也是 Circle 的一个实例。同时要注意,引用变量 o和 c指向同一个对象,而在进行基本数据类型转换时,会创建一个新的对象。
为什么没有在一开始就把 myObject定义为 Circle 类型呢?为了能够进行通用程序设计,一个好的经验是把变童定义为父类型,这样,它就可以接收任何子类型的值。
注意::对象成员访问运算符( .)优先于类型转换运算符。使用圆括号保证在点运算符( .)之前进行转换,例如:((Circle)object).getArea();