二、super 关键字
1)第一种用法:super 关键字是父类对象的引用
package com.example; public class Person{ // 父类 public String name="张三"; public int age = 20; public void speak(){ System.out.println("Person: speak()"); } public void cry(){ System.out.println("Person: cry()"); } }
package com.example; public class Man extends Person{ // 子类 public void speak(){ System.out.println("Man: speak()"); super.speak(); // 调用父类的 speak cry(); // 调用父类的 cry System.out.println("name: " + name); // 调用父类的 name System.out.println("age: " + super.age); // 调用父类的 age } }
package com.example; public class Test{ public static void main(String[] args){ Man m = new Man(); m.speak(); } }
运行结果: Man: speak() Person: speak() Person: cry() name: 张三 age: 20
- 父类和子类中都有 speak 方法,所以子类中想要调用父类的 speak 方法时,必须要用 super 关键字(这种情况是子类对父类的方法进行了重写)
- 其他情况可以使用 super 关键字也可以不使用,例如:例子中的 cry(), name, super.age
- 其实在创建子类对象的时候,父类对象会先被创建,而且父类对象被包裹在子类对象中(可以想象成在 Man 对象中有 Person super = new Person() 这样的代码行,这里的 super 就是父类对象的引用)
2)第二种用法:super() 或者 super(参数列表) 是父类的构造器
例1:
package com.example; public class Person{ // 父类 public Person(){ System.out.println("Person()"); } }
package com.example; public class Man extends Person{ // 子类 public Man(){ super(); // 父类构造器 System.out.println("Man()"); // super(); //error } }
package com.example; public class Test{ public static void main(String[] args){ new Man(); } }
运行结果: Person() Man()
- 父类构造器只能在子类构造器中调用,不能在子类的方法中调用
- 父类构造器只能位于子类构造器的第一行
- 每个子类构造器只能调用一个父类构造器
例2:
package com.example; public class Person{ // 父类 public Person(int i){ System.out.println("Person" + "(" + i + ")"); } public Person(String str){ System.out.println("Person" + "(" + str + ")"); } }
package com.example; public class Man extends Person{ // 子类 public Man(){ super(1); // 调用父类构造器 System.out.println("Man()"); } public Man(int i){ super("张三"); // 调用父类构造器 System.out.println("Man" + "(" + i + ")"); } public Man(String str){ super(2); // 调用父类构造器 System.out.println("Man" + "(" + str + ")"); } }
package com.example; public class Test{ public static void main(String[] args){ new Man(); } }
运行结果: Person(1) Man()
- 如果我们没有给类创建构造器,编译器会为该类自动创建一个默认构造器(无参构造器),该操作在编译时完成,我们看不见。如果我们给类创建了构造器,那么编译器便不会再为类创建默认构造器
- 当父类中含有默认构造器时,编译器会在子类的构造器中自动调用父类的默认构造器,以完成父类的初始化,该操作也是在编译时完成的,我们看不见
- 当父类中含有无参构造器时,编译器也会在子类构造器中自动调用父类的无参构造器
- 当父类中只含有有参构造器时,编译器不会在子类构造器中自动调用父类的构造器,此时我们必须在子类的所有构造器中都对父类中的一个构造器进行调用,否则编译时会报错,例如:例子中子类 Man 中的每一个构造器都调用了父类的构造器
- 总结:在继承关系中,父类最好不创建构造器或者在创建多个构造器时要创建一个无参构造器,这样才不用每个子类的构造器都对父类的构造器进行调用
三、访问控制权限
1)Java 的访问控制权限有 public,protected,<default>,private 其中如果没有涉及继承关系时,protected 和 <default> 都只能在包内访问
package net.example; public class S{ public int i = 10; protected double d = 51.2; String str1 = "张三"; // <default> private String str2 = "李四"; }
package com.example; public class T{ public int i = 10; protected double d = 51.2; String str1 = "张三"; // <default> private String str2 = "李四"; }
package com.example; import net.example.S; // 导入 S 类 public class Test{ public static void main(String[] args){ S s1 = new S(); System.out.println("S: " + s1.i); //System.out.println("S: " + s1.d); // error //System.out.println("S: " + s1.str1); // error //System.out.println("S: " + s1.str2); // error T t1 = new T(); System.out.println("T: " + t1.i); System.out.println("T: " + t1.d); System.out.println("T: " + t1.str1); //System.out.println("T: " + t1.str2); // error } }
运行结果: S: 10 T: 10 T: 51.2 T: 张三
- private 可以跨包访问
- 没有继承关系时,protected 和 <default> 都只能在包内访问。例子中 Test 和 T 在同一个包,Test 和 S 在不同的包,所以 s1.d 和 s1.str1 都不能通过编译,而 t1.d 和 t1.str1 可以通过编译
- private 只能在同一个类中被访问
2)子类可以访问父类中用 protected 限制的属性和方法,即使子类和父类不在同一个包内
package net.example; public class Animals{ // 父类 protected int age = 2; String name = "旺财"; // <default> protected void bark(){ System.out.println("汪汪"); } }
package com.example; import net.example.Animals; // 导入 Animals 类 public class Dogs extends Animals{ // 子类 public Dogs(){ System.out.println(age); //System.out.println(name); // error bark(); } }
package com.example; public class Test{ public static void main(String[] args){ new Dogs(); } }
运行结果: 2 汪汪
- 父类中的 name 属性的访问控制权限为 <default> ,所以子类不能调用该属性
- protected 访问控制权限可以保证只有继承者才能对父类的属性和方法进行访问,当然这是在跨包的情况下,如果某一个类和父类在同一个包,就算这个类不继承父类,那么也可以随意访问父类中由 protected 限制的属性和方法
3)子类要重写父类中的方法时,子类中重写的方法的访问控制权限必须要高于父类的方法的访问控制权限
package com.example; public class Person{ // 父类 public void p1(){ System.out.println("Person: p1"); } protected void p2(){ System.out.println("Person: p2"); } void p3(){ System.out.println("Person: p3"); } private void p4(){ System.out.println("Person: p4"); } }
package com.example; public class Man extends Person{ // 子类 public void p1(){ System.out.println("Man: p1"); } /* public void p2(){ System.out.println("Man: p2"); } */ protected void p2(){ System.out.println("Man: p2"); } /* void p2(){ // error, protected 的访问控制权限比 <default> 的高 System.out.println("Man: p2"); } */ /* public void p3(){ System.out.println("Man: p3"); } protected void p3(){ System.out.println("Man: p3"); } */ void p3(){ System.out.println("Man: p3"); } /* public void p4(){ System.out.println("Man: p4"); } protected void p4(){ System.out.println("Man: p4"); } void p4(){ System.out.println("Man: p4"); } */ private void p4(){ System.out.println("Man: p4"); } }
package com.example; public class Test{ public static void main(String[] args){ Man m = new Man(); m.p1(); m.p2(); m.p3(); //m.p4(); // error } }
运行结果: Man: p1 Man: p2 Man: p3
- 子类 Man 中的 p2 方法不能用 <default> 访问控制权限进行重写,因为 protected 的控制权限比较高
- Test 类中不能调用 m.p4() 因为 p4() 的访问控制权限是 private,只能在类的内部进行调用
- 子类重写父类的方法时,只要重写方法的访问控制权限比父类中的高就行,比如:父类中的 p2 方法由 protected 限制,所以子类 Man 中重写父类的 p2 方法时,可以是public 或者是 protected,但不能是 <default> 和 private
四、初始化顺序
1)初始化顺序:父类静态属性 -> 子类静态属性 -> 父类非静态属性 -> 父类构造器调用 -> 子类非静态属性 -> 子类构造器调用
package com.example; public class T{ public T(int i){ System.out.println("T" + "(" + i + ")"); } }
package com.example; public class Person{ // 父类 public static T t1 = new T(1); // 静态属性 public T t2 = new T(2); // 非静态属性 public Person(){ System.out.println("Person()"); } public static T t3 = new T(3); // 静态属性 }
package com.example; public class Man extends Person{ // 子类 public static T t4 = new T(4); // 静态属性 public T t5 = new T(5); // 非静态属性 public Man(){ System.out.println("Man()"); } public static T t6 = new T(6); // 静态属性 }
package com.example; public class Test{ public static void main(String[] args){ new Man(); } }
运行结果: T(1) T(3) T(4) T(6) T(2) Person() T(5) Man()
五、@Override 与方法重写
1)Java 为了保证方法重写时不会出现误写成方法重载的情况,所以引入了 @Override
package com.example; public class Person{ // 父类 public void speak(int i){ System.out.println("Person: speak" + "(" + i + ")"); } }
package com.example; public class Man extends Person{ // 子类 /* @Override public void speak(String str){ System.out.println("Man: speak" + "(" + str + ")"); } */ // error,用了 @Override ,那么 @Override 下面的方法必须是方法重写,否则编译时会出错 @Override public void speak(int i){ System.out.println("Man: speak" + "(" + i + ")"); } }
package com.example; public class Test{ public static void main(String[] args){ Man m = new Man(); m.speak(10); } }
运行结果: Man: speak(10)
六、继承抽象类
1)继承抽象类时,如果父类中有抽象方法,那么子类必须全部重写父类中的抽象方法
package com.example; public abstract class Person{ public abstract void p1(); // 抽象方法 public abstract void p2(); public void p3(){ System.out.println("Person: p3()"); } public void p4(){ System.out.println("Person: p4()"); } }
package com.example; public class Man extends Person{ @Override public void p1(){ } @Override public void p2(){ } }
七、final 关键字
1)由 final 关键字修饰的类不能被继承,例如:public final class Person{}
2)在继承关系中,如果父类的方法由 final 关键字修饰,那么该方法将不能被子类重写
参考资料:
《Java 编程思想》第4版