Java中使用类对现实世界中实体来进行描述,类经过实例化之后的产物对象,则可以用来表示现实中的实体,现实世界错误复杂,事物之间可能会存在一些联系,比如:狗和猫,它们都是一个动物。
按我们之前学到,猫和狗有一些相同的动物属性,代码写起来就比较冗余,所以引出继承。
继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。
继承最大的意义:对代码可以进行复用。
子类 extends 父类:is-a 的关系
修饰符 class 子类 extends 父类 {
// …
}
// Animal.java
public class Animal{
String name;
int age;
public void eat(){
System.out.println(name + "正在吃饭");
}
public void sleep(){
System.out.println(name + "正在睡觉");
}
}
// Dog.java
public class Dog extends Animal{
void bark(){
System.out.println(name + "汪汪汪~~~");
}
// Cat.Java
public class Cat extends Animal{
void mew(){
System.out.println(name + "喵喵喵~~~");
}
}
// TestExtend.java
public class TestExtend {
public static void main(String[] args) {
Dog dog = new Dog();
// dog类中并没有定义任何成员变量,name和age属 性肯定是从父类Animal中继承下来的
System.out.println(dog.name);
System.out.println(dog.age);
// dog访问的eat()和sleep()方法也是从Animal中继承下来的
dog.eat();
dog.sleep();
dog.bark();
}
}
注意:
(1)子类和父类不存在同名成员变量
子类又有,就拿子类的;子类没有,就拿父类的。
//父类
public class Base {
int a;
int b;
}
//子类
public class Derived extends Base{
int c;
public void method(){
a = 10; // 访问从父类中继承下来的a
b = 20; // 访问从父类中继承下来的b
c = 30; // 访问子类自己的c
}
}
(2)子类和父类成员变量同名
如果父类和子类有重名的成员变量,优先使用子类的(就近原则)。
//父类
public class Base {
int a = 1;
int b = 2;
int c = 3;
}
//子类
public class Derived extends Base{
int a; // 与父类中成员a同名,且类型相同
char b; // 与父类中成员b同名,但类型不同
public void method(){
a = 100; // 访问子类自己新增的a
b = 101; // 访问子类自己新增的b
c = 102; // 子类没有c,访问的肯定是从父类继承下来的c
// d = 103; // 编译失败,因为父类和子类都没有定义成员变量b
super.a; //是超类,暂且理解为引用的父类(就想访问父类)
}
}
在子类方法中 或者 通过子类对象访问成员时:
成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找。
在成员方法名不同时,在子类方法中或者通过子类对象访问方法时:
①优先访问自己的;
②如果自己没有,再到父类找;
③如果父类中也没有,则报错。
(1)成员方法名字不同
//父类
public class Base {
public void methodA(){
System.out.println("Base中的methodA()");
}
}
//子类
public class Derived extends Base{
public void methodB(){
System.out.println("Derived中的methodB()方法");
}
public void methodC(){
methodB(); // 访问子类自己的methodB()
methodA(); // 访问父类继承的methodA()
// methodD(); // 编译失败,在整个继承体系中没有发现方法methodD()
}
}
(2)成员方法名字相同
通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法传递参数选择合适的方法访问,如果没有则报错;
//父类
public class Base {
public void methodA(){
System.out.println("Base中的methodA()");
}
public void methodB(){
System.out.println("Base中的methodB()");
}
}
//子类
public class Derived extends Base{
public void methodA(int a) {
System.out.println("Derived中的method(int)方法");
}
public void methodB(){
System.out.println("Derived中的methodB()方法");
}
public void methodC(){
methodA(); // 没有传参,访问父类中的methodA()
methodA(20); // 传递int参数,访问子类中的methodA(int)
methodB(); // 直接访问,则永远访问到的都是子类中的methodB(),基类的无法访问到
super.methodB(); //访问父类的methodB()方法
}
}
主要作用:在子类方法中访问父类的成员。
super只是一个关键字,最大的作用其实是在你写代码或者读代码的时候,体现出更好的可读性!
【注意事项】:
父子父子,先有父再有子。
即:子类对象构造时,需要先调用基类构造方法,将从基类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整。
//父类/基类
public class Base {
public Base(){
System.out.println("Base()");
}
}
//子类
public class Derived extends Base{
public Derived(){
// super(); // 注意子类构造方法中默认会调用基类的无参构造方法:super(),
// 用户没有写时,编译器会自动添加,而且super()必须是子类构造方法中第一条语句,
// 并且只能出现一次
System.out.println("Derived()");
}
}
public class Test {
public static void main(String[] args) {
Derived d = new Derived();
}
}
执行结果:
Base()
Derived()
在继承关系上,我们什么构造方法都没写的时候,编译器会有默认的以下两个构造方法:
//父类
public Animal() {
}
//子类
public Dog() {
super();
}
其中,关于super:
① 注意子类构造方法中默认会调用基类的无参构造方法:super();
② 用户没有写时,编译器会自动添加,而且super()必须是子类构造方法中第一条语句;
③ super只能出现一次,且不能和this同时出现。
【注意】
① 若父类显示定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构造方法;
② 如果父类构造方法是带参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败;
在调用构造方法的时候,不能同时出现!
【相同点】
【不同点】
之前我们学的实例代码块和静态代码块,在没有继承关系时的执行顺序。
class Person {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("构造方法执行");
}
{
System.out.println("实例代码块执行");
}
static {
System.out.println("静态代码块执行");
}
}
public class TestDemo {
public static void main(String[] args) {
Person person1 = new Person("张三",10);
System.out.println("============================");
Person person2 = new Person("李四",20);
}
}
执行结果:
静态代码块执行
实例代码块执行
构造方法执行
====================
实例代码块执行
构造方法执行
【继承关系上的执行顺序】
class Person {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Person:构造方法执行");
}
{
System.out.println("Person:实例代码块执行");
}
static {
System.out.println("Person:静态代码块执行");
}
}
class Student extends Person{
public Student(String name,int age) {
super(name,age);
System.out.println("Student:构造方法执行");
}
{
System.out.println("Student:实例代码块执行");
}
static {
System.out.println("Student:静态代码块执行");
}
}
public class TestDemo4 {
public static void main(String[] args) {
Student student1 = new Student("张三",19);
System.out.println("===========================");
Student student2 = new Student("gaobo",20);
}
//执行结果
Person:静态代码块执行
Student:静态代码块执行
Person:实例代码块执行
Person:构造方法执行
Student:实例代码块执行
Student:构造方法执行
===========================
Person:实例代码块执行
Person:构造方法执行
Student:实例代码块执行
Student:构造方法执行
通过分析执行结果,得出以下结论:
① 父类静态代码块优先于子类静态代码块执行,且是最早执行;
②父类实例代码块和父类构造方法紧接着执行;
③ 子类的实例代码块和子类构造方法紧接着再执行;
④第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行。
父类优先于子类执行,静态的最先执行。
在类和对象中,为了实现封装特性,Java中引入了访问限定符,主要限定:类或者类中成员能否在类外或者其他包中被访问。
受保护的,继承上
1. 同类
package demo1;
public class TestDemo1 {
protected int a = 99;
public static void main(String[] args) {
TestDemo1 testDemo1 = new TestDemo1();
System.out.println(testDemo1.a);
}
}
2. 同包不同类
package demo1;
public class TestDemo1 {
protected int a = 99;
}
package demo1;
public class TestDemo2 {
public static void main(String[] args) {
TestDemo1 testDemo1 = new TestDemo1();
System.out.println("不同类" + testDemo1.a);
}
}
3. 不同包的子类
在不同包的继承子类中,注意:
① 不能直接在main方法中引用,因为他是受保护的,所以要使用super发来访问;
② 新建一个方法写super,因为静态方法中不能用super。
package demo1;
public class TestDemo1 {
protected int a = 99;
private int b;
}
package demo2;
import demo1.TestDemo1;
public class TestDemo3 extends TestDemo1 {
public void func() {
//再写一个方法放super,不直接写道main方法中,是因为main方法是静态方法,静态方法中不能放super
System.out.println(super.a);
// System.out.println(super.b); //编译报错,父类private成员再相同包子类中不可见
}
public static void main(String[] args) {
TestDemo3 testDemo3 = new TestDemo3();
testDemo3.func();
}
}
其中:类必须都是public
注意:父类中private成员变量虽然在子类中不能直接访问,但是也继承到子类中了。
什么时候下用哪一种呢?
我们希望类要尽量做到“封装”,即隐藏内部实现细节,只需暴露出必要的信息给类的调用者。
因此我们在使用的时候,应尽可能的使用比较严格的访问权限。例如,如果一个方法能用private,就尽量不要用public。
另外还有一种粗暴的做法:将所有的字段设为 private,将所有的方法设为 public。 不过这种方式属于是对访问权限的滥用,我们在写代码的时候,还是要认真思考该类提供的字段方法到底给“谁”使用(是类内部自己用,还是类的调用者使用,还是子类使用)
【单继承】
public class A {
……
}
public class B extends A {
……
}
【多层继承】
public class A {……}
public class B extends A {……}
public class C extends B {……}
【不同类继承同一个类】
public class A {……}
public class B extends A {……}
public class C extends A {……}
【多继承】(不支持)
public class A {……}
public class B {……}
public class C extends A , B {……} //错误,Java不支持多继承
注意:Java中不支持多继承。
一般我们不希望出现超过三层的继承关系,如果继承层次太多,就需要考虑对代码进行重构了。
如果想从语法上进行限制继承,就可以使用final关键字。
final关键字可以用来修饰变量、成员方法以及类。
1. 修饰变量或字段,表示常量(即不能修改)
一般常量名要大写。
final int A = 10;
A = 20; // 编译出错
如果是成员变量定义为final,语法规定必须要给一个初始值。
class Animal {
public final String name = "123";
}
关于数组:
final int[] array = {1,2,3};
array = new int[10]; //报错
array[0] = 19; //正确
被final修饰的array这个引用指向的对象是不能改变的,但是可以改变里面的值。
2. 修饰类:表示此类不能被继承
final public class Animal {
...
}
public class Bird extends Animal {
...
}
// 编译出错
Error:(3, 27) java: 无法从最终com.bit.Animal进行继
我们平时使用的String字符串类,就是final修饰,不能被继承。
3. 修饰方法:表示该方法不能被重写
组合:是一种代码的实现方式,设计思想。
面向对象的三大特性:封装、继承、多态。
继承表示对象之间是 is - a 的关系,如:狗是动物,猫是动物
组合表示对象之间是 has - a 的关系,如:汽车和轮胎、发动机、方向盘等的关系就是组合,因为汽车是由这些不见组成的。
在一个类里面,会将另一个类当作这个类的属性,用的比较多!
// 学生类
class Student{
}
// 教师类
class Teacher{
}
//教学楼类
class House{
}
// 学校类
class School{
public Student[] students = new Student[3];// 可以复用学生中的属性和方法
public Teacher[] teachers = new Teacher[3];// 可以复用教师中的属性和方法
}
// 一中是学校
class YiZhong extend School{
// 将学校中包含的:学生、教师全部继承下来
}
public class Test {
public static void main(String[] args) {
School school = new School();
}
}
在一个类里面,会将另一个类作为我当前类的属性,这个设计思想就是组合,也相当于是代码的复用。我们日常用的比较多,如:接口、循序表等都有组合的思想在其中。
其中上述代码中,main函数中的内存情况如图所示: