跳转至

面向对象(二)

一、封装性

1. 封装性的体现

当创建一个类的对象以后,我们可以通过对象.属性的方式对对象的属性进行赋值,这里赋值操作受到数据类型和存储范围的制约,除此之外没有其他制约条件。但是在实际问题中,往往需要给属性赋值加入额外的限制条件,这个条件就不能在属性声明时体现,我们只能通过方法添加限制条件,比如setAge()。同时需要避免用户再使用对象.属性对属性进行赋值,则需要将属性声明为私有的(private)。同时提供方法getAge()获取属性。此时对于属性就体现了封装性。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class AnimalTest {
    public static void main(String[] args) {
        Animal cat = new Animal();
        cat.name = "Tom";
//        cat.age = 5;
        cat.setAge(5); // 通过方法赋值,验证数据的有效性
        cat.show();
    }

}

class Animal {
    String name;
    //    int age;
    private int age; // 通过权限修饰符,变成私有属性(封装性的体现)

    // 属性的设置
    public void setAge(int a) {
        if (a > 0) {
            age = a;
        } else {
            age = 0;
        }
    }

    // 属性的获取
    public int getAge() {
        return age;
    }

    public void show() {
        System.out.println("name:" + name + " age:" + age);
    }
}

2. 权限修饰符

封装性的体现需要权限修饰符来配置。

权限/作用域 类内部 同一个包 不同包的子类 同一个工程
private × × ×
缺省 × ×
protected ×
public

这4种权限可以用来修修饰类及内部结构:属性、方法、构造器、内部类。修饰类的话只能使用public缺省

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.example.www;

public class Person {
    private int personPrivate;
    int personDefault;
    protected int personProtected;
    public int personPublic;

    private void methodPrivate(){
        personPrivate = 1;
        personDefault = 2;
        personProtected = 3;
        personPublic = 4;
    }
    void methodDefault(){
        personPrivate = 1;
        personDefault = 2;
        personProtected = 3;
        personPublic = 4;
    }
    protected void methodProtected(){
        personPrivate = 1;
        personDefault = 2;
        personProtected = 3;
        personPublic = 4;
    }
    public void methodPublic(){
        personPrivate = 1;
        personDefault = 2;
        personProtected = 3;
        personPublic = 4;
    }
}

private测试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package com.example.www;

public class HelloPerson {
    public static void main(String[] args) {
        Person p = new Person();

        // 同一个包中不可以调用Person类中私有的属性、方法
//        p.personPrivate = 1; // java: personPrivate 在 com.example.www.Person 中是 private 访问控制
//        p.methodPrivate(); // java: methodPrivate() 在 com.example.www.Person 中是 private 访问控制

        p.personDefault = 2;
        p.personProtected = 3;
        p.personPublic = 4;

        p.methodDefault();
        p.methodProtected();
        p.methodPublic();
    }
}

缺省测试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package com.example.api;

import com.example.www.Person;

public class Student extends Person {
    public void methodStudent(){
        personProtected = 1;
        personPublic = 2;

        methodProtected();
        methodPublic();
//        在不同包的子类中不能调用Person类中private和缺省权限的属性、方法

//        personPrivate = 3;
//        personDefault = 4;
//        methodPrivate();
//        methodDefault();
    }
}

protected测试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package com.example.web;

import com.example.www.Person;

public class HelloWorld {
    public static void main(String[] args) {
        Person p = new Person();
        p.personPublic = 1;
        p.methodPublic();

//        不同包下的普通类(非子类)要使用Person类,不可以调用声明为private、protected、缺省权限的属性、方法
//        p.personPrivate = 2;
//        p.personDefault = 3;
//        p.personProtected = 4;
//
//        p.methodPrivate();
//        p.methodDefault();
//        p.methodProtected();

    }
}

3. this关键字

1. 作用

  1. 可以把this理解为当前对象或当前正在创建的对象
  2. 用来调用属性和方法、构造器

2. 修饰属性和方法

  1. 在类的方法(或构造器)中可以使用this.属性this.方法的方式调用调用当前对象(或当前正在创建的水下)的属性或方法。但是通常情况下,都选择省略this.。特殊情况下如果方法(或构造器)的形参和类的属性同名,则必须显式的使用this.变量的方法,表明此变量是属性,而非形参。

3. 调用构造器

  1. 在类的构造器中,可以显示的使用this(形参列表)的方法调用本类中指定的其他构造器。
  2. 构造器中不能通过this(形参列表)的方法调用自己。
  3. 如果一个类中有n个构造器,则最多有n-1个构造器中使用了this(形参列表)
  4. 构造器内部最多只能声明一个this(形参列表),用来调用其他构造器。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class AnimalTest {
    public static void main(String[] args) {
        Animal cat = new Animal();
        cat.name = "Tom";
        cat.setAge(5);
        cat.show(); // name:Tom age:5

        Animal mouse = new Animal("Jerry", 6);
        mouse.show(); // name:Jerry age:6
    }

}

class Animal {
    String name;
    private int age;

    public Animal(){

    }
    public Animal(String name){
        this.name = name;
    }
    public Animal(String name, int age){
        this(name);
        this.age = age;
    }
    public void setAge(int age) {
//        age = age;  // name:Tom age:0
        this.age = age; // name:Tom age:5
        // this 表示当前对象
    }

    public void show() {
        System.out.println("name:" + name + " age:" + age);
    }
}

二、继承性

1. 作用

  1. 减少代码冗余,提高代码的复用性
  2. 便于功能扩展
  3. 为多态性的使用提供前提

2. 格式

1
2
3
class A extends B{

}

A:子类、派生类、subclass

B:父类、超类、基类、superclass

一旦子类A继承父类B之后,子类A就获取了父类B中声明的所有属性和方法。父类中声明的private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构,只是因为封装性的影响,使得子类不能直接调用父类的结构。

子类继承父类以后,还可以声明自己特有的属性和方法,实现功能的扩展。

3. 特点

  1. 一个类可以被多个子类继承
  2. 单继承性:一个类只能有一个父类
  3. 子父类是相对的概念
  4. 子类直接继承的父类称为父类,间接继承的父类称为间接父类
  5. 子类继承父类以后,就获取了父类以及所有间接父类中声明的所有属性和方法

4. 使用

Person父类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package com.example.www;

public class Person {
    String name;
    int age;
    public Person(){

    }
    public Person(String name, int age){
        this.name = name;
        this.age = age;
    }

    public void eat(){
        System.out.println("eating");
    }
    public void sleep(){
        System.out.println("sleeping");
    }
}

Student子类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package com.example.www;

public class Student extends Person{
    String major;
    public Student() {

    }
    public Student(String name, int age, String major) {
        this.name = name;
        this.age = age;
        this.major = major;
    }

    public void study(){
        System.out.println("studying");
    }

    public void show(){
        System.out.println("name:" + name + " age:" + age); // 继承了父类的属性
    }
}

测试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package com.example.www;

public class ExtendsTest {
    public static void main(String[] args) {
        Student stu = new Student();
        stu.name = "Tom";
        stu.age = 16;
        stu.major = "English";
        stu.study();  // studying
        stu.show();  // name:Tom age:16
    }
}

5. Object

  1. 如果没有显示的声明一个类的父类,此时类继承于java.lang.Object
  2. 所有java类(除java.lang.Object之外)都直接或间接继承于java.lang.Object类。
  3. 所有的Java类都具有java.lang.Object类声明的功能。
  4. 常用方法:equals()toString()getClass()hashCode()

toString()的使用:

当输出一个对象的引用时,实际上是调用当前对象的toString()

ObjecttoString()的定义

1
2
3
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

String、Date、File、包装类等都重写了Object中的toString()。使得在调用对象的toString()时,返回“实体内容信息”。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class HelloWorld {
    public static void main(String[] args) {
        Person p = new Person("Tom", 8);
        System.out.println(p.toString()); // Person@1b6d3586
        System.out.println(p); // Person@1b6d3586

        String str = new String("Hello");
        System.out.println(str); // Hello
    }
}

class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

自定义类也可以重写toString()方法。当调用此方法时,返回对象的“实体内容”。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class HelloWorld {
    public static void main(String[] args) {
        Person p = new Person("Tom", 8);
        System.out.println(p.toString()); // Person{name='Tom', age=8}
        System.out.println(p); // Person{name='Tom', age=8}
    }
}

class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    // 重写toString()
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

在实际开发中利用IDE自动生成。

6. 方法重写

在子类中根据需要对从父类中继承来的方法进行改造。在程序执行时,子类的方法将覆盖父类的方法。

重写以后,当创建子类对象以后,通过子类对象调用父类中同名同参数的方法时,实际执行的是子类重写父类的方法。

重写的规定:

  1. 子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参相同
  2. 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符。特殊情况:子类不能重写父类中声明为private权限的方法。
  3. 返回值类型
    1. 父类被重写的方法的返回值是void,则子类重写的方法的返回值只能是void
    2. 父类被重写的方法的返回值是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类。
    3. 父类被重写的方法的返回值是基本数据类型,则子类重写的方法的返回值类型必须是相同的基本数据类型
  4. 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型

子类和父类中的同名同参数的方法要么声明为非static的(考虑重写),要么都声明为static的(不是重写)。

Person父类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package com.example.www;

public class Person {
    String name;
    int age;
    public Person(){

    }
    public Person(String name, int age){
        this.name = name;
        this.age = age;
    }

    public void eat(){
        System.out.println("eating");
    }
}

Student子类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package com.example.www;

public class Student extends Person{
    String major;
    public Student() {

    }
    public Student(String name, int age, String major) {
        this.name = name;
        this.age = age;
        this.major = major;
    }

    public void eat() { // 重新父类的方法
        System.out.printf("Eat healthy food");
    }
}

测试继承性

1
2
3
4
5
6
7
8
package com.example.www;

public class ExtendsTest {
    public static void main(String[] args) {
        Student stu = new Student("Tom", 16, "English");
        stu.eat();  // Eat healthy food
    }
}

7. super关键字

1. 作用

用来调用父类的属性、方法、构造器。

2. 调用父类的属性和方法

  1. 在子类的方法或构造器中通过super.属性super.方法的方式显式的调用父类中声明的属性或方法。通常情况下省略super.
  2. 当子类和父类中定义了同名的属性时,想要早子类中调用父类中声明的属性时,则必须显式使用super.属性的方式,表明调用的是父类中声明的属性。
  3. 当子类重写了父类中的方法以后,当想在子类的方法中调用父类中被重写的方法时,则必须显式的使用super.方法的方式,表明调用的是父类中被重写的方法。

3. 调用构造器

  1. 可以子类的构造器中显式的使用super(形参列表)的方法,调用父类中声明的指定构造器
  2. super(形参列表)必须声明在子类构造器的首行
  3. 在类的构造器中,针对this(形参列表)super(形参列表)只能二选一,不能同时出现
  4. 在构造器的首行,没有显式的声明this(形参列表)super(形参列表),默认调用的是父类中的空参构造器。
  5. 在类的多个构造器中,至少有一个类的构造器中使用了super(形参列表)调用父类中的构造器。

4. 示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package com.example.www;

public class Person {
    String name;
    int age;

    public Person(){
        System.out.println("I am PersonClass");
    }

    public Person(String name, int age){
        this.name = name;
        this.age = age;
    }

    public void eat(){
        System.out.println("eating");
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package com.example.www;

public class Student extends Person{
    String major;
    public Student(){

    }
    public Student(String name, int age, String major){
        super(name, age);
        this.major = major;
    }
    public void eat(){
        super.eat();
        System.out.println("Eat Healthy food");
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package com.example.www;

public class StudentTest {
    public static void main(String[] args) {
        Student stu1 = new Student("Tom", 16, "English");
        stu1.eat();
//        eating
//        Eat Healthy food
        Student stu2 = new Student(); // I am PersonClass 默认调用的是父类中的空参构造器。
    }
}

8. 子类对象的实例化过程

1. 从结果上看

  1. 子类继承父类以后,就获取了父类中声明的属性和方法
  2. 创建子类的对象,在堆空间中会加载所有父类中声明的属性。

2. 从过程上看

当通过子类的构造器创建子类对象时,一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,直到调用了java.lang.Object类中的空参构造器为止。正因为加载过所有父类的结构,所以才可以看到内存中有父类的结构,子类对象才可以考虑调用。

虽然创建子类对象时,调用了父类的构造器,但是自始至终只创建了一个对象,即new的子类对象。

三、多态性

1. 多态性

可以理解为一个事物的多种形态。多态性指的是对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)。

2. 多态性的使用

有了对象的多态性以后,在编译器只能调用父类中声明的方法,在运行期,实际执行的是子类重写父类的方法(编译看左右,运行看右边)。

对象的多态性只适用于方法,不适用于属性(边和和运行都看左边)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//public class AnimalTest {
//    public static void main(String[] args) {
//        // 对象的多态性:父类的引用指向子类的对象
//        Animal a1 = new Cat();
//        // 多态的使用,当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法 -- 虚拟方法调用
//        a1.eat(); // 吃鱼
////        a1.catchMouse();  // 错误 只能调用Animal中定义过的
//    }
//}

public class AnimalTest {
    public static void main(String[] args) {
        // 对象的多态性:父类的引用指向子类的对象
        Animal a1 = new Cat();
//         多态的使用,当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法 -- 虚拟方法调用
        a1.eat(); // 吃鱼
//        a1.catchMouse();  // 错误 只能调用Animal中定义过的


        // 多态的使用
        AnimalTest test = new AnimalTest();
        test.func(new Cat());  // 吃鱼
        test.func(new Dog());  // 吃骨头

    }

    public void func(Animal animal) {
        animal.eat();
    }
}

class Animal {
    public void eat() {
        System.out.println("吃");
    }
}

class Cat extends Animal {
    public void eat() {
        System.out.println("吃鱼");
    }

    public void catchMouse() {
        System.out.println("抓老鼠");
    }
}

class Dog extends Animal {
    public void eat() {
        System.out.println("吃骨头");
    }

    public void catcheFrisbee() {
        System.out.println("接飞盘");
    }
}

3. 多态的使用前提

  1. 类的继承关系
  2. 方法的重写

4. 虚拟方法调用

子类中定义了与父类同名同参数的方法。在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法,这样的方法调用时在编译期无法确定的。

5. 向上转型和向下转型

1. 向下转型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package com.example.www;

class Animal {
}

class Cat extends Animal {
    public void catchMouse() {
        System.out.println("抓老鼠");
    }
}

public class HelloWorld {
    public static void main(String[] args) {
        Animal a = new Cat();
        // 不能调用子类特有的
        a.catchMouse();  // IDEA中提示:Cannot resolve method 'catchMouse' in 'Animal'
    }
}

执行编译报错

1
2
3
4
5
6
HelloWorld.java:13: error: cannot find symbol
        a.catchMouse();
         ^
  symbol:   method catchMouse()
  location: variable a of type Animal
1 error

有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时只能调用父类中声明的属性和方法,子类特有的属性和方法不能调用。

所以需要使用强制类型转换符(向下转型),这样就能调用子类特殊的属性和方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Animal {
}

class Cat extends Animal {
    public void catchMouse() {
        System.out.println("抓老鼠");
    }
}

public class HelloWorld {
    public static void main(String[] args) {
        Animal a = new Cat();
        Cat tom = (Cat) a;  // 向下转型:使用强制类型转换符
        tom.catchMouse();  // 抓老鼠
    }
}

当试图将对象强制转换为不是实例的子类时,可能出现ClassCastException的异常。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Animal {
}

class Cat extends Animal {
    public void catchMouse() {
        System.out.println("抓老鼠");
    }
}

class Dog extends Animal {
    public void catcheFrisbee() {
        System.out.println("接飞盘");
    }
}
public class HelloWorld {
    public static void main(String[] args) {
        Animal a = new Cat();
        Cat tom = (Cat) a;  // 向下转型:使用强制类型转换符
        tom.catchMouse();  // 抓老鼠
//        Dog Snoopy = (Dog) a;  // Exception in thread "main" java.lang.ClassCastException: Cat cannot be cast to Dog

    }
}

为了避免出现这样的问题,可以使用instanceof关键字。

格式:对象名 instanceof 类名:判断对象是否是类的实例,如果是返回true,否则返回false

使用场景:为了避免在向下转型时出现异常,在向下转型前使用instanceof进行判断,返回true进行向下转型。

如果a instanceof A返回truea instanceof B也返回true,其中类B是类A的父类。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Animal {
}

class Cat extends Animal {
    public void catchMouse() {
        System.out.println("抓老鼠");
    }
}

class Dog extends Animal {
    public void catchFrisbee() {
        System.out.println("接飞盘");
    }
}

public class HelloWorld {
    public static void main(String[] args) {
        HelloWorld test = new HelloWorld();
        test.func(new Cat());  // 抓老鼠
        test.func(new Dog());  // 接飞盘

    }

    public void func(Animal animal) {
        if (animal instanceof Cat) {
            Cat a = (Cat) animal;
            a.catchMouse();
        }
        if (animal instanceof Dog) {
            Dog a = (Dog) animal;
            a.catchFrisbee();
        }
    }
}

四、包装类

针对8种基本数据类型定义相应的引用数据类型--包装类(封装类)。

基本数据类型 包装类
byte Byte
short Short
int Integer
long Long
float Float
double Double
boolean Boolean
char Character

Java提供了8种基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征。

基本数据类型、包装类、String之间的转换

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class WrapperTest {
    public static void main(String[] args) {
        // 基本数据类型-->包装类:调用包装类的构造器
        int num = 10;
        Integer i = new Integer(num);
        System.out.println(i.toString()); // 10

        Integer j = new Integer("123");
        System.out.println(j.toString()); // 123

        Boolean b1 = new Boolean(true);
        System.out.println(b1.toString());  // true  看源码
        Boolean b2 = new Boolean("xxx");
        System.out.println(b2.toString());  // false


        // 包装类-->基本数据类型:调用包装类的xxxValue()
        Integer n = new Integer(12);
        int m = n.intValue();
        System.out.println(m + 3);  // 15

        // 自动装箱与自动拆箱(JDK5.0)
        // 自动装箱:基本数据类型-->包装类
        int a = 5;
        Integer b = a;
        // 自动拆箱:包装类-->基本数据类型
        int c = b;


        // 基本数据类型、包装类-->String
        int k = 10;
        // 方式一:连接运算
        String str1 = k + "";
        // 方式二:调用String重载在ValueOf(xxx)
        float f = 1.23f;
        String str2 = String.valueOf(f);


        // String-->基本数据类型、包装类
        String s = "123";
        // 调用包装类的parseXxx()
        int x = Integer.parseInt(s);
    }
}

五、抽象类和抽象方法

abstract关键字:

  1. 用来修饰类和方法。
  2. 不能用来修饰私有方法、静态方法、final的方法、final的类

1. 抽象类

  1. 抽象类不能实例化
  2. 抽象类中一定有构造器,便于子类实例化时调用
  3. 开发中都会提供抽象类的子类,让子类对象实例化,完成相关的操作。

2. 抽象方法

1
public abstract void func();
  1. 抽象方法只方法声明,没有方法体
  2. 包含抽象方法的类一定是抽象类,反之,抽象类中可以没有抽象方法
  3. 若子类重写了父类中的所有抽象方法后,此类方可实例化。如果没有重写父类所有的抽象方法,此类也是一个抽象类
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class AbstractTest {
    public static void main(String[] args) {
        Cat Tom = new Cat("Tom", 6);
        Tom.eat();
        Dog Snoopy = new Dog("Snoopy", 8);
        Snoopy.eat();
    }
}

abstract class Animal {
    String name;
    int age;

    public abstract void eat();
}

class Cat extends Animal {

    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void eat() {
        System.out.println("吃鱼");
    }
}

class Dog extends Animal {
    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void eat() {
        System.out.println("吃骨头");
    }
}

3. 匿名子类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class PersonTest {
    public static void main(String[] args) {
        // 创建了一个匿名子类的对象:p
        Person p = new Person(){
            public void method(){
            }
        };
    }
}

abstract class Person{
    public abstract void method();
}

六、接口

Java不支持多重继承,有了接口,就可以实现多重继承的效果。

1. 使用

  1. 使用interface关键字定义
  2. 接口和类是并列的两个结构
  3. 接口中不能定义构造器,意味着接口不可以实例化
  4. 接口通过让类实现(implements)的方式来使用,如果实现类覆盖了接口中所有的抽象方法,则此类就可以实例化
  5. 接口的主要用途就是被实现类实现(面向接口编程)
  6. 可以实现多个接口,弥补了Java单继承的局限性
  7. 接口与接口直接可以继承,而且可以多继承
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class InterfaceTest {
    public static void main(String[] args) {
        Plane plane = new Plane();
        plane.fly();  // 起飞...
        Bullet bullet = new Bullet();
        bullet.attack();
    }
}

// 定义一个接口
interface Flyable {
    // 全局常量
    public static final int MAX_SPEED = 7900;

    // 抽象方法
    public abstract void fly();
}

interface Attackable {
    void attack();
}


// 实现接口
class Plane implements Flyable {
    // 实现接口中的方法
    public void fly() {
        System.out.println("起飞...");
    }
}

// 实现多个接口
class Bullet extends Object implements Flyable, Attackable {
    public void fly(){
        System.out.println("飞行");
    }
    public void attack(){
        System.out.println("攻击");
    }
}

2. 定义

2.1 JDK7及以前

  1. 全局常量:public static final的,可以不写
  2. 抽象方法:public abstract
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
/*
* 接口的使用
* 1. 接口在使用上也满足多态性
* 2. 接口,实际上就是定义了一种规范
* */

public class USBTest {
    public static void main(String[] args) {
        Computer computer = new Computer();
        // 创建接口的非匿名实现类的非匿名对象
        Flash flash = new Flash();
        computer.transferData(flash);

        // 创建接口的非匿名实现类的匿名对象
        computer.transferData(new Printer());

        // 创建接口的匿名实现类的非匿名对象
        USB phone = new USB() {
            @Override
            public void start() {
                System.out.println("手机开始工作");
            }

            @Override
            public void end() {
                System.out.println("手机结束工作");
            }
        };
        computer.transferData(phone);

        // 创建接口的匿名实现类的匿名对象

        computer.transferData(new USB() {
            @Override
            public void start() {
                System.out.println("mp3开始工作");
            }

            @Override
            public void end() {
                System.out.println("mp3结束工作");
            }
        });



    }
}


class Computer{
    public void transferData(USB usb){ // USB usb = new Flash()  多态的体现
        usb.start();
        System.out.println("传输数据");
        usb.end();
    }
}


interface USB{
    void start();
    void end();
}

class Flash implements USB {

    @Override
    public void start() {
        System.out.println("U盘开始工作");
    }

    @Override
    public void end() {
        System.out.println("U盘结束工作");
    }
}

class Printer implements USB {

    @Override
    public void start() {
        System.out.println("打印机开始工作");
    }

    @Override
    public void end() {
        System.out.println("打印机结束工作");
    }
}

2.2 JDK8

  1. 全局常量:public static final
  2. 抽象方法:public abstract
  3. 静态方法
  4. 默认方法
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// 静态方法和抽象方法
public class InterfaceTest {
    public static void main(String[] args) {

        // 1. 接口中定义的静态方法,只能通过接口来调用
        MyInterface.method1();  // 静态方法:method1

        // 2. 通过实现类的对象,可以调用接口中的默认方法
        // 如果实现类重写了接口中的默认方法,调用时仍然调用的是重写后的方法
        SubClass obj = new SubClass(); // SubClass:method2
        obj.method2();

        // 3. 如果子类或实现类继承的父类和实现的接口声明了同名同参数的方法
        // 那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法 -- 类优先原则
        obj.method3(); // SuperClass:method3

        // 4. 如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法
        // 那么在实现类没有重写此方法的情况下,会报错 -- 接口冲突
        // 就必须在实现类中重新此方法

        obj.method5();
    }
}

interface MyInterface {
    // 静态方法

    public static void method1() {
        System.out.println("静态方法:method1");
    }

    // 默认方法
    public default void method2() {
        System.out.println("默认方法:method2");
    }

    public default void method3() {
        System.out.println("默认方法:method3");
    }

}

class SuperClass {
    public void method3() {
        System.out.println("SuperClass:method3");
    }
}

class SubClass extends SuperClass implements MyInterface {
    public void method2() {
        System.out.println("SubClass:method2");
    }

    // 5. 在子类(或实现类)的方法中调用父类、接口中被重写的方法
    public void method5(){
        // 调用自己定义的重写的方法
        method2();
        // 调用父类中声明的方法
        super.method3();
        // 调用接口中的默认方法
        MyInterface.method1();
    }
}