浅谈Java面向对象编程之多态、继承、抽象类、接口

概览

1. 包

  • 包 (package) 是组织类的一种方式。
  • 使用包的主要目的是保证类的唯一性。

1.1 导入包中的类

java中提供了很多现成的类供我们使用

public class Test {
    public static void main(String[] args) {
        java.util.Date date = new java.util.Date();
        // 得到一个毫秒级别的时间戳
        System.out.println(date.getTime());
   }
}

这种方式写法比较麻烦一些,可以使用import语句导包

import java.util.Date;
public class Test {
    public static void main(String[] args) {
        Date date = new Date();
        // 得到一个毫秒级别的时间戳
        System.out.println(date.getTime());
   }
}

如果需要使用java.util中的其他类,可以使用 import java.util.*,但有的情况下更建议显示的指定要导入的类名,负责容易出现冲突的状况

import java.util.*;
import java.sql.*;
public class Test {
    public static void main(String[] args) {
        // util 和 sql 中都存在一个 Date 这样的类, 此时就会出现歧义, 编译出错
        Date date = new Date();
        System.out.println(date.getTime());
   }
}
// 编译出错
Error:(5, 9) java: 对Date的引用不明确
  java.sql 中的类 java.sql.Date 和 java.util 中的类 java.util.Date 都匹配

在这种情况下使用完整的类名:

import java.util.*;
import java.sql.*;
public class Test {
    public static void main(String[] args) {
        java.util.Date date = new java.util.Date();
        System.out.println(date.getTime());
   }
}

注意:

  • import 和 C++ 的 #include 差别很大。C++ 必须 #include 来引入其他文件内容, 但是 Java 不需要。
    import 只是为了写代码的时候更方便。 import 更类似C++的namespace和using。
  • java.long包下的内容不需要导入,编译器会自动导入。

1.2 静态导入

使用 import static 可以导入包中的静态的方法和字段。
例如:

import static java.lang.System.*;
import static java.lang.Math.*;
public class Test {
    public static void main(String[] args) {
        out.println("hello");
        double x = 30;
        double y = 40;
        //静态导入的方式更方便一些
        // double result = Math.sqrt(pow(x, 2) + Math.pow(y, 2));
         double result = sqrt(pow(x, 2) + pow(y, 2));
        System.out.println(result);
   }
}

1.3 将类放到包中

基本规则:

  • idea中在src上右键->新建->Packeg ,包名需要尽量指定成唯一的名字,通常会使用公司的域名的颠倒形式。
  • 包名与代码路径相匹配,在文件最上方加上package语句指定该代码在哪个包中。
  • 如果一个类中没有package语句,则该类被放到一个默认包中。

1.4 包的访问权限控制

如下图所示:

浅谈Java面向对象编程之多态、继承、抽象类、接口

  • private:类内部可以访问,类外不能访问
  • 默认(也叫包访问权限):类内部能访问,同一个包中的类可以访问,其他类不能访问
  • protected:类内部能访问,子类和同一包中的类可以访问,其他类不能访问
  • public:类内部和类的调用者都能访问

1.5 常见的系统包

  1. java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。
  2. java.lang.reflect:java 反射编程包;
  3. java.net : 进行网络编程开发包。
  4. java.sql:进行数据库开发的支持包。
  5. java.util:是java提供的工具程序包。(集合类等) 非常重要
  6. java.io:I/O编程开发包

2. 继承

程序层面上:表达继承的关系使用extends
意义:

  • 减少代码量,代码复用
  • 降低了代码冗余

2.1 语法规则

基本语法:

class 子类 extends 父类 {}
        派生类             基类
                                超类

  • 使用 extends 指定父类。
  • java中只支持单继承
  • 子类继承了父类除构造方法以外所有的东西
  • 一般来说不会超过三层的继承关系

注意:
如果父类写有有参的构造方法,则子类要显示的调用super()来帮助父类来构造,并且调用super()必须放在第一行

代码示例:

class Animal { 
 public String name; 
 public Animal(String name) { 
 this.name = name; 
 } 
 public void eat(String food) { 
 System.out.println(this.name + "正在吃" + food); 
 } 
} 
class Cat extends Animal { 
 public Cat(String name) { 
 // 使用 super 调用父类的构造方法. 
 // super表示对父类的引用
 super(name); 
 } 
} 
class Bird extends Animal { 
 public Bird(String name) {
        super(name); 
 } 
 public void fly() { 
 System.out.println(this.name + "正在飞 ︿( ̄︶ ̄)︿"); 
 } 
} 
public class Test { 
 public static void main(String[] args) { 
 Cat cat = new Cat("小黑"); 
 cat.eat("猫粮"); 
 Bird bird = new Bird("圆圆"); 
 bird.fly(); 
 } 
}

如果将父类属性由public改为private,那么子类将不能访问

2.2 protected关键字

  • 对于类的调用者来说, protected 修饰的字段和方法是不能访问的。
  • 对于类的子类和同一个包的其他类来说, protected 修饰的字段和方法是可以访问的。
  • 同一包中的同一类、同一包中的不同类、不同包中的子类可以访问。

2.3 final关键字

  • 在final修饰一个变量或者字段的时候,表示常量(不能修改)。
  • final关键字也能修饰类,此时表示被修饰的类就不能被继承。

代码示例:

final public class Animal { 
 ... 
} 
public class Bird extends Animal { 
 ... 
} 
// 编译出错
Error:(3, 27) java: 无法从最终com.wx.Animal进行继承

那么由此可见,final关键字的功能是限制类被继承,用final修饰的类被继承的时候就会编译报错,平时用的String类就是final修饰的不能被继承。

3. 组合

和继承类似,组合也是一种表达类之间关系的方式。

代码示例:

public class Student { 
 ... 
} 
public class Teacher { 
 ... 
} 
public class School { 
 public Student[] students; 
 public Teacher[] teachers; 
}

组合并没有涉及到extends,仅仅是将一个类的实例作为另外一个类的字段,这是设计类的常用方式之一。

  • 组合通常表示 has-a 语义
  • 继承通常表示 is-a 语义

4.多态

父类引用子类对象,通过父类引用来调用子类中重写的方法,此时就会发生动态绑定,也叫做多态。

4.1 向上转型

将派生类的值赋值给基类,即父类引用引用子类对象
向上转型的表现形式:

  • 直接赋值
在上面的例子中:
Bird bird = new Bird("圆圆");

代码也可以写成这样子:
Animal bird = new Bird("圆圆");

此时bird是一个父类的引用,指向一个子类(Bird)的实例,这种写法称为向上转型

  • 方法传参
public class Test { 
 public static void main(String[] args) { 
 	Bird bird = new Bird("圆圆"); 
 	feed(bird);//传递bird 使用Animal来接收
 } 
 public static void feed(Animal animal) { 
 	animal.eat("谷子"); 
 } 
} 
// 执行结果
圆圆正在吃谷子
  • 方法返回:
public class Test { 
 public static void main(String[] args) { 
 	Animal animal = findMyAnimal(); //使用Animal来接收bird
 } 
 public static Animal findMyAnimal() { 
 	Bird bird = new Bird("圆圆"); 
 	return bird; 
 } 
}

4.2 动态绑定

运行时多态,编译时调用父类方法,运行时调用的是子类重写的方法

  • 父类引用,引用子类的对象
  • 通过父类的引用,调用子类和父类重写的那个方法

注意:

  • 当父类引用子类对象的时候,只能访问自己特有的,访问不到子类。
  • 通过引用可以调用方法,重点看引用类型

4.3 方法重写

子类实现父类的同名方法,并且参数的类型和个数完全相同,这种情况称为覆写/重写/覆盖(Override)

注意:

  • 重写和重载完全不一样,不要混淆
  • 普通方法可以重写, static 修饰的静态方法不能重写
  • 重写中子类的方法的访问权限不能低于父类的方法访问权限
  • 重写的方法返回值类型不一定和父类的方法相同(但是建议最好写成相同, 特殊情况除外)
  • 针对重写的方法,可以使用@Override来显示指定

重载和重写的区别:
浅谈Java面向对象编程之多态、继承、抽象类、接口
小结:

  • 重载:在同一个类中

    • 方法名相同
    • 参数列表不同
    • 返回值不做要求
  • 重写:

    • 方法名相同
    • 参数列表 相同(参数个数、参数的类型)
    • 返回值也要相同

注意:

  • 需要重写的方法,不能是private修饰的
  • 被final所修饰的方法,不能被重写
  • 需要重写方法的,对于访问修饰限定符,子类的访问修饰限定符一定要大于或等于父类的访问修饰限定符
  • static修饰的静态方法不能重写

4.4 向下转型

向上转型是子类对象转成父类对象,向下转型就是父类对象转成子类对象。相比于向上转型来说,向下转型没那么常见,但是也有一定的用途。

  • 父类对象赋值给子类引用
  • 在使用之前使用instanceof 可以判定一个引用是否是某个类的实例. 如果是, 则返回true,此时可以进行向下转型

4.5 super和this关键字

  • super:表示获取到父类实例的引用
  • this:表示获取当前对象的引用

super和this的区别:
浅谈Java面向对象编程之多态、继承、抽象类、接口

5. 抽象类

  • 抽象类存在的意义就是为了被继承
  • 抽象类也可以发生向上转型及多态

5.1 抽象方法

被abstract所修饰的方法,没有具体的实现

5.2 抽象类

5.2.1 语法规则

abstract class Shape { 
 	abstract public void draw(); 
}

5.2.2 抽象类的作用

包含抽象方法的类称为抽象类
注意:

  • 抽象类是不可以被实例化的。
  • 抽象类中可以有成员方法和成员变量,比普通类多了抽象方法
  • 如果一个普通类继承了一个抽象类,那么这个普通类一定要重写抽象类中的抽象方法
  • 抽象方法一定要重写,不能用private,final修饰
  • 抽象类继承抽象类不需要重写抽象类的方法,如果有其他类继承了这个抽象类,那么一定要重写这个抽象方法
  • 抽象类也可以发生向上转型和动态绑定

6. 接口

接口是抽象类的更进一步,抽象类中还可以包含非抽象方法和字段,而接口方法中包含的方法都是抽象方法,字段只能包含静态常量。

6.1 语法规则

interface IShape { 
	 void draw(); //抽象方法
}
  • 使用interface定义一个接口
  • 一个类只能继承一个父类,但是可以实现多个接口
  • 所有的方法默认是public abstract
  • 所有的成员变量一定是public static final的
  • 接口也不可以实例化
  • 类和接口的关系:implements,可以实现多个接口,每个接口之间使用逗号隔开
  • 接口也可以进行向上转型,多态
  • 接口之间可以继承

JDK1.8新特性:

  • 如果接口中的方法被default修饰,则这个方法可以有具体实现。

6.2 Comparable接口

对自定义类型进行排序,需要实现Comparable接口,对compareTo方法进行重写

java中提供了关于对象的比较,有以下方式:

  • equals:所有类都具备的能力
    理解:比较两个对象代表的是不是一个实际事物,需要通过重写equals方法才能达到

  • 大小的比较

    • 自然顺序 Comparable compareTo( ) ,不是所有类都具备,需要通过实现Comparable接口来表明该类具备这个能力
    • 外部比较 ( 比较器) Comparator,构造一个天平,不需要比较的类具备Comparable的能力
    • 理解:比较传入的两个引用指向的对象

代码示例1:实现Comparable接口

class Teacher implements Comparable<Teacher>{
    public String name;
    public int age;
    public int height;
    public int weight;

    public Teacher(String name, int age, int height, int weight) {
        this.name = name;
        this.age = age;
        this.height = height;
        this.weight = weight;
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    //按照age进行比较
    @Override
    public int compareTo(Teacher o) {
        return this.age-o.age;
    }
}
public class ComparableTestDemo {
    public static void main(String[] args) {
        Teacher[] teachers = new Teacher[3];
        teachers[0] = new Teacher("li",12,180,80);
        teachers[1] = new Teacher("li1",15,175,67);
        teachers[2] = new Teacher("li2",3,167,90);

        System.out.println(teachers[0]+" "+teachers[1]+" "+teachers[2]);


        Arrays.sort(teachers);

        System.out.println(teachers[0]+" "+teachers[1]+" "+teachers[2]);


    }
}
//运行结果:
Teacher{name='li', age=12} Teacher{name='li1', age=15} Teacher{name='li2', age=3}
Teacher{name='li2', age=3} Teacher{name='li', age=12} Teacher{name='li1', age=15}

代码示例2:实现Comparator接口、外部比较器

class ByHeigthComparator implements Comparator<Teacher>{

    @Override
    public int compare(Teacher o1, Teacher o2) {
        return o1.height-o2.height;
    }
}
class ByWeightComparator implements Comparator<Teacher>{

    @Override
    public int compare(Teacher o1, Teacher o2) {
        return o1.weight-o2.weight;
    }
}
public class ComparatorDemo {
    public static void main(String[] args) {
        Teacher[] teachers = new Teacher[3];
        teachers[0] = new Teacher("li",12,180,80);
        teachers[1] = new Teacher("li1",15,175,67);
        teachers[2] = new Teacher("li2",3,167,90);

        Comparator<Teacher> byHeight = new ByHeigthComparator();
        Comparator<Teacher> byweight = new ByWeightComparator();
        System.out.println(teachers[0].compareTo(teachers[1]));
        System.out.println(byHeight.compare(teachers[0],teachers[1]));
        System.out.println(byweight.compare(teachers[0],teachers[1]));
    }
}
//运行结果:
-3
5
13

6.3 Clonable接口

Object 类中存在一个 clone 方法, 调用这个方法可以创建一个对象的 “拷贝”。但是要想合法调用 clone 方法, 必须要先实现 Clonable 接口, 否则就会抛出 CloneNotSupportedException 异常。

  • 空接口:标记接口,标记当前类是可以克隆的。
  • 实现Clonable接口,并且重写clone方法

代码示例:

Person类:

class Money implements Cloneable{
    public double money;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class Person implements Cloneable{
    public String name;
    public Money m ;//类中有引用类型,需要将引用类型也进行拷贝
    public Person(String name) {
        this.name = name;
        this.m =new Money(); 
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person person = (Person)super.clone();
        person.m = (Money)this.m.clone(); //需要将 m也进行克隆
        return person;
    }
}

Demo类:

public class Demo {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person("name");
        person1.m.money = 12.5;
        Person person2 = (Person) person1.clone();

        System.out.println(person2);

        person1.name = "lisi";

        System.out.println(person2);

        System.out.println(person1.m.money);
        System.out.println(person2.m.money);

        person1.m.money = 18;

        System.out.println(person1.m.money);
        System.out.println(person2.m.money);

    }
}

运行结果:
12.5
12.5
18
12.5

6.4 抽象类和接口的区别

核心区别:

  • 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法。

图示:
浅谈Java面向对象编程之多态、继承、抽象类、接口

上一篇:json的Java解析


下一篇:Java学习SE基础之包(import语句)