Java面向对象(下)

本文深入探讨了面向对象编程的三大特性:封装、继承和多态。讲解了如何通过封装保护数据,使用继承减少代码重复,以及多态在代码中的应用。还详细介绍了Java中的类变量、类方法、构造器、代码块以及抽象类和接口。此外,讨论了final关键字的作用,以及如何防止滥用继承。最后,通过实例展示了如何利用多态进行程序设计,包括多态数组和参数,以及类变量和类方法的使用。
摘要由CSDN通过智能技术生成

👻👻介是小白飘飘记录韩顺平老师的Java视频中的面向对象的后半部分笔记(286~422),如有不妥请指出🎈🎈



在这里插入图片描述

封装

把抽象出的数据(属性)和对数据的操作(方法)封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作(方法),再能对数据进行操作。

封装的三个步骤

● 将属性进行私有化(private)
● 提供一个公共的(public)set方法,用于对属性判断并赋值
● 提供一个公共的(public)get方法,用于获取属性的值

public class objectOriented {
    public static void main(String[] args) {
        Account p = new Account("sherry", 1, "zbc");
//        String name = p.setName("sherry");
//        double balance = p.setBalance(1);
//        String password = p.setPassword("abc");

        System.out.println(p.name + '\t' + p.balance + '\t' + p.password);


    }
}
class Account {
    String name;
    double balance;
    String password;

    Account(String name, double balance, String password) {
        setName(name);
        setBalance(balance);
        setPassword(password);
    }

    Account() {
    }


    public String setName(String name) {
        // 长度只能是2,3,4位
        if (name.length() > 4 || name.length() < 2) {
            this.name = "香飘飘";
            return this.name;
        } else {
            return name;
        }
    }

    public double setBalance(double balance) {
        // balance > 20
        if (balance < 20) {
            this.balance = 20;
            return this.balance;
        } else {
            return balance;
        }
    }

    public String setPassword(String password) {
        // 密码长度必须大于6位,否则警告并设为默认值
        if (password.length() < 6) {
            this.password = "123456";
            return this.password;
        }
        return password;
    }
}

继承🔔

继承的引出(代码冗余度高,下面小栗子中的小学生与大学生类几乎是一样的)

public class objectOriented {
    public static void main(String[] args) {
        pupil p = new pupil("小椰", 22);
        p.test();
        p.setScore(90);
        p.info();
        undergraduate wills = new undergraduate("沐沐", 24);
        wills.test();
        wills.setScore(91);
        wills.info();

    }
}
/*
模拟小学生考试情况
 */
class pupil {
    public String name;
    public int age;
    private double score;

    pupil (String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void setScore(double score) {
        this.score = score;
    }
    public void test() {
        System.out.println("001号小学生" + name + "正在考数学。。。");
    }
    public void info() {
        System.out.println(name + '\t' + age + '\t' + score);
    }
}

class undergraduate {
    public String name;
    public int age;
    private double score;

    undergraduate (String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void setScore(double score) {
        this.score = score;
    }
    public void test() {
        System.out.println("001号大学生" + name + "正在考数学分析。。。");
    }
    public void info() {
        System.out.println(name + '\t' + age + '\t' + score);
    }

}


001号小学生小椰正在考数学。。。
小椰	22 	90.0
001号大学生沐沐正在考数学分析。。。
沐沐	24	91.0

如何解决上面代码出现的问题:
😃😃继承可以解决代码复用,让我们的编程更靠近人类思维,当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends继来声明承父类。
继承示意图

  • 基本语法
class 子类 extends 父类 {}

Java 与 C++ 定义继承类的方式十分相似。Java 用关键字extends代替了 C++ 中的冒号 : ,在 Java 中,所有的继承都是公有继承,而没有 C++ 中的私有继承和保护继承。
下面例子中尽管student类是一个超类,但并不是因为它优于子类或者拥有比子类更多的功能。 实际上恰恰相反,子类比超类拥有的功能更加丰富

public class objectOriented {
    public static void main(String[] args) {
        pupil p = new pupil("小耶", 22);
        p.test();
        p.setScore(99);
        p.info();
        System.out.println("=============");
        undergraduate q = new undergraduate("沐沐", 23);
        q.test();
        q.setScore(100);
        q.info();

    }
}
/*
模拟小学生考试情况
 */
//父类
class student {
    public String name;
    public int age;
    private double score;

    student (String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void setScore(double score) {
        this.score = score;
    }

    public void info() {
        System.out.println(name + '\t' + age + '\t' + score);
    }
}
//子类:小学生pupil
class pupil extends student{

    pupil(String name, int age) {
        super(name, age);
    }

    public void test() {
        System.out.println("小学生正在考小学数学。。。");
    }
}
//子类:大学生:undergraduate
class undergraduate extends student {

    undergraduate(String name, int age) {
        super(name, age);
    }

    public void test() {
        System.out.println("大学生正在考数学分析。。。");
    }
}


小学生正在考小学数学。。。
小耶	22	99.0
=============
大学生正在考数学分析。。。
沐沐	23	100.0

子类继承父类

1.子类访问父类的私有属性与方法

子类继承了所有属性和方法,非私有的属性和方法可以直接在子类访问,但私有属性和方法不能直接在子类访问,要通过公共方法(接口)去访问。

  • 上面代码的student类中的score属性是私有的,我们直接访问是不行的,可以在父类中创建一个公共的方法返回属性score的值。

2.子类必须调用父类的构造器,完成父类的初始化

子类无参构造器中隐藏的super()默认调用父类的无参构造器
● 当创建子类对象时,不管子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类中没有提供无参构造器,则必须在子类的构造器中用super去指定使用父类的构造器完成对父类的初始化,否则编译不通过。

public class objectOriented {
    public static void main(String[] args) {
        pupil p1 = new pupil();
        System.out.println("=========");
        pupil p2 = new pupil("沐沐", 23);
    }
}

/*
模拟小学生考试情况
 */
//父类
class student {
    public String name;
    public int age;
    private double score;

    student(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("父类的构造器student(String name, int age)被调用。。。");
    }
    //无参构造器
    student() {
        System.out.println("父类的无参构造器被调用。。。");
    }
}

//子类:小学生pupil
class pupil extends student {

    pupil() {
        System.out.println("子类pupil无参构造器被调用。。。");
    }

    pupil(String name, int age) {

        super(name, age);
        System.out.println("子类pupil构造器pupil(String name, int age)被调用。。。");
    }

    public void test() {

        System.out.println("小学生正在考小学数学。。。");
    }
}


父类的无参构造器被调用。。。
子类pupil无参构造器被调用。。。
=========
父类的构造器student(String name, int age)被调用。。。
子类pupil构造器pupil(String name, int age)被调用。。。

把父类的构造器注释掉后,编译会报错
注释父类的构造器

3.super关键字

因为 super 不是一个对象的引用,不能将 super 赋给另一个对象变量,它只是一个指示编译器调用超类方法的特殊关键字

  • 用途
  1. 访问父类的属性,但不能直接访问私有属性。(super.属性名
  2. 调用父类的方法。当父类和子类都具有相同的方法名时,可以使用 super 关键字访问父类的方法。但不能直接访问私有方法。(super.方法名
  3. 调用父类的构造器。调用构造器的语句只能作为另一个构造器的第一条语句出现 。构造参数既可以传递给本类( this) 的其他构造器,也可以传递给超类(super ) 的构造器 。(super(***))
  • 注意事项与使用细节
  1. super()必须是子类构造器的首行
  2. super与this不能同时出现。(这两都要放首行,在一起会打起来!!!)
  3. 子类构造器中super(形参1, 形参2, …); 会找父类中对应参数个数的构造器执行以完成初始化。
  4. super的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用super去访问爷爷类的成员,如果多个父类中都有同名的成员,使用super访问遵循就近原则
  • this与super区别
区别thissuper
访问属性访问本类中的属性,如果本类中没有此属性则从父类中继续查找从父类开始查找属性
调用方法访问本类中的方法,如果本类中没有此方法则从父类中继续查找从父类开始查找方法
调用构造器调用本类构造器,必须放在构造器首行调用父类构造器,必须放在子类构造器的首行
特殊表示当前对象子类中访问父类对象

this 指的是当前对象的引用,super 是当前对象的父对象的引用。如果构造>方法的第一行代码不是 this() 和super(),则系统会默认添加 super()。
● super 关键字的用法:
super.父类属性名:调用父类中的属性
super.父类方法名:调用父类中的方法
super():调用父类的无参构造方法
super(参数):调用父类的有参构造方法
● this 关键字的用法:
this.属性名:表示当前对象的属性
this.方法名(参数):表示调用当前对象的方法

4.Java中所有类都是Object类的子类,Object类是顶级父类

  • Object类存储在java.lang包中,使用的时候无需显示导入,编译时由编译器自动导入。是所有java类(Object类除外)的终极父类,不过接口不继承Object类。
  • 可以使用类型为Object的变量指向任意类型的对象。Object类的变量只能用作各种值的通用持有者,要对他们进行任何专门的操作,都需要知道它们的原始类型并进行类型转换。

5.super调用父类成员不限于直接父类,一直追溯到顶级父类Object

● 子类最多只能继承一个父类(直接继承),Java中是单继承机制。让A类继承B类和C类:先让A继承B,再让B继承C。

6.不能滥用继承,子类和父类必须满足 is-a 的逻辑关系

有一个用来判断是否应该设计为继承 关系的简单规则,这就是“ is-a”规则,它表明子类的每个对象也是超类的对象,比如每个经理都是雇员,反之,并不是每个雇员都是经理。“ is-a”规则的另一种表述法是置换法则。它表明程序中出现超类对象的任何地方都可以用子类对象置换。

7.阻止继承:final关键字

● final 修饰的类不能被继承,但是可以实例化对象。
● final 修饰的方法不能被子类重写,但是可以被继承。

public class objectOriented {
    public static void main(String[] args) {
        A a = new A();
        a.out();
    }
}

class B {
    //B类不是final类,但含有final方法。该方法虽然不能重写,但可以被继承
    public final void out() {
        System.out.println("final修饰的out()方法。。。");
    }
}

class A extends B {

}
  • final 修饰的变量(成员变量或局部变量)即成为常量,只能赋值一次。
  • final 修饰的成员变量必须在声明的同时赋值,如果在声明的时候没有赋值,那么只有 一次赋值的机会,而且只能在构造方法中显式赋值,然后才能使用。
  • final 修饰的局部变量可以只声明不赋值,然后再进行一次性的赋值:
    ○ final修饰的普通属性
    在声明时赋值
    在普通代码块中赋值
    在构造器中赋值
    ○ final修饰的静态属性
    在声明时赋值
    在静态代码块中赋值
class A {
    //定义时直接赋值
    public final double MY_NUM = 777;
    //在构造器中赋值
    public final double MY_NUM2;

    public A(double MY_NUM2) {
        this.MY_NUM2 = MY_NUM2;
    }
    //在代码块中赋值
    public final double MY_NUM3;
    
    {
        this.MY_NUM3 = 666;
    }

}

class B {
    //定义时直接赋值
    public static final double MY_NUM = 777;
    //在代码块中赋值
    public static final double MY_NUM3;

    static {
        MY_NUM3 = 666;
    }
}

● 如果一个类已经被final修饰,就没有必要再其含有的方法用final修饰。
final不能修饰构造器
● final往往与static搭配使用(如不会导致类的加载,仅加载修饰的成员)。
下面两段代码的区别:

public class objectOriented {
    public static void main(String[] args) {
        System.out.println(B.num);
    }
}

class B {
    public static int num = 123;
    static {
        System.out.println("B 的静态代码块被执行");
    }
}


B 的静态代码块被执行
123
public class objectOriented {
    public static void main(String[] args) {
        System.out.println(B.num);
    }
}

class B {
    public final static int num = 123;
    static {
        System.out.println("B 的静态代码块被执行");
    }
}


123

继承的本质

  • 小栗子
public class objectOriented {
    public static void main(String[] args) {
        Son son = new Son();
        //1.先看子类Son是否有该属性
        //2.若子类Son中有该属性并且可以访问,直接返回
        //3.若子类Son的中没有这个属性,就看其父类中是否有该属性(若有并且可以访问就返回该信息)
        //4.父类Father中没有按照3一直追溯到Object,都没有就要报错
        System.out.println(son.name);
        System.out.println(som.age);
        System.out.println(son.label);
    }
}
//超类
class GrandFa {
    String name = "小耶";
    String label = "小财迷";
    int age = 19;
}
//父类1
class Father extends GrandFa {
    String name = "沐沐";
    private int age = 20;
}
//子类2
class Son extends Father{
    String name = "狗总";
    String hobby = "口嗨";
}


狗总
19
小财迷

在这里插入图片描述

(1)在new Son();时先加载的是其父类,最先加载的是Object顶级父类,再加载GrandFa父类,再加载Father父类,再加载Son
(2)访问属性信息时,就近原则。没有就找父类,一直追溯到Object
(3)当父类的属性被 private 修饰时变成私有的了,那上面的Father中的属性age,依然在堆中对象里,只是不能访问(只能在本类访问),又回到上面说的,让Father提供一个公有的方法,通过Son继承后再访问

方法重写/覆盖

子类有一个方法和父类的某个方法的名称、返回类型、参数一样,那么就称子类的方法覆盖了父类的方法。
(1)子类的方法的形参列表方法名称,要和父类方法的参数,方法名称完全一样
(2)子类方法的返回类型和父类方法返回类型一样,或者是父类的返回类型的子类。比如父类返回类型是Object,子类返回类型是String
(3)子类不能方法不能缩小父类方法的访问权限,允许相等或扩大。

方法覆盖

重载与重写区别

名称发生范围方法名形参列表返回类型修饰符
重载(overload)本类必须一样类型、个数或者顺序至少有一个不同无要求无要求
重写(override)父子类必须一样相同返回类型一样,或者是父类的返回类型的子类子类不能方法不能缩小父类方法的访问权限,允许相等或扩大

  • 重写(override)小栗子
public class Rose {
    public static void main(String[] args){
        Student p =new Student("小耶", 20, "1001", 90);
        String res = p.say();
        System.out.println(res);
    }
}

class Person {
    private String name;
    private int age;

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

    public String say() {
        return "name=" + name + "\tage=" + age;
    }
}

class Student extends Person {
    private String id;
    private double score;

    public Student(String name, int age, String id, double score) {
        super(name, age);
        this.id = id;
        this.score = score;
    }

    public String say() {
        return super.say() + "\tid=" + id + "\tscore=" + score;
    }
}


name=小耶	age=20	id=1001	score=90.0

Object类

equals()方法

● ==和equals的区别
(1)==是一个比较运算符。既可以判断基本类型又可以判断引用类型。如果判断基本类型,判断的是值是否相等。如果判断引用类型,判断的是地址相是否等,即判断是不是同一个对象
(2)equals()方法用于检测一个对象是否等于另外一个对象,只能判断引用类型。在子类中定义eqauls()方法时,首先调用超类的eqauls()方法。如果检测失败,对象就不可能相等。如果超类中的域都相等,就需要比较子类的实例域(域就是java里的字段,也叫属性或成员变量)。
● equals()特性
(1)自反性:对于任何非空引用x,x.equals(x)应该返回true。
(2)对称性:对于任何引用x和y,当且仅当y.equals(x)返回true,x.equals(y)也应该返回true。
(3) 传递性:对于任何引用x,y,z,如果x.equals(y)返回true,y.equals(z)返回true,x.equals(z)也应该返回true。
(4)一致性:如果x和y引用的对象没有发生变化,反复调用x.equals(y)应该返回相同的结果。
(5)对于任意非空引用xx.equals(null)应该返回false。

//Object中的equals()
public boolean equals(Object obj) {
    return (this == obj);	//默认比较地址,即是否判断同一对象
}
//String中的equals()把Object中的equals()方法重写了,变成了两个字符串的值是否相等
public boolean equals(Object anObject) {
    if (this == anObject) {	//如果是同一对象直接返回true
        return true;
    }
    if (anObject instanceof String) {	//判断类型
        String anotherString = (String)anObject;	//向下转型
        int n = value.length;
        if (n == anotherString.value.length) {	//如果长度相同
            char v1[] = value;	//转成char数组,一个一个比较字符
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;	//如果两个字符串的所有字符都相等,则返回true
        }
    }
    return false;	//如果比较的不是字符串,则返回false
}
//Integer中也重写了Object的equals()方法,变成判断两个值是否相等
public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}
public class Test {
    public static void main(String[] args) {
        Person p1 = new Person("鬼鬼", 19, '女');
        Person p2 = new Person("鬼鬼", 19, '女');
        //未重写equals方法返回的是false
        System.out.println(p1.equals(p2));
    }
}

class Person {
    private String name;
    private int age;
    private char gender;

    public Person(String name, int age, char gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
    //重写Object的equals()
    public boolean equals(Object obj) {

        if (this == obj) {  //先判断是否是同一个对象,是就直接返回true
            return true;
        }
        if (obj instanceof Person) {    //类型判断,是Person我们才比较
            Person p = (Person) obj; //向下转型
            return this.name.equals(p.name) && this.age == p.age && this.gender == p.gender;
        }
        return false;

    }
}


true

hashcode()方法

hash
hashcode
● 小结
(1)提高具有哈希结构的容器的效率。
(2)两个引用,如果指向的是同一个对象,则哈希值肯定是一样的;如果指向的是不同对象,则哈希值是不一样的。
(3)哈希值主要根据物理地址得到,但不能将哈希值当等价于地址。hashcode代表对象的地址说的是对象在hash表中的位置,物理地址说的是对象在内存中的位置。通过对象的物理地址转换成一个整数,然后该整数通过hash函数得到hashcode。
(4)在集合中也会向equals()那样进行重写。

toString()方法

(1)返回该对象的字符串表示
(2)Object中的toString()方法(默认方法)返回:全类名(包名+类名) + @ + 哈希值的十六进制。换句话说,该方法返回一个字符串,他的值等于:getClass().getName() + ‘@’ + Integer.toHexString(hashCode())
(3)子类往往重写toString()方法,用于返回对象的属性信息。
(4)当直接输出一个对象时,toString()方法会被默认调用。

public class Rose {
    public static void main(String[] args) {
        Monster ms = new Monster("鬼鬼", 19, 20000);
        //默认返回:Monster@1b6d3586	hashcode=460141958
        //重写后:Monster{name='鬼鬼', age=19, salary=20000.0}	hashcode=460141958
        System.out.println(ms.toString() + "\thashcode=" + ms.hashCode());
    }
}

class Monster {
    private String name;
    private int age;
    private double salary;

    public Monster(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }
    //重写toString()方法,输出对象的属性
    // alt + ins -> toString
    @Override
    public String toString() {
        return "Monster{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", salary=" + salary +
                '}';
    }
}

finalize()方法

● 当垃圾回收器确定不存在对该改对象的更多引用时,由对象的垃圾回收器调用此方法。
(1)当对象被回收时,系统自动调用该对象的finalize()方法,子类可以重写该方法,做一些释放资源的操作。
(2)什么时候被回收:当某个对象没有任何引用时,则jvm就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁对象前,会调用finalize()方法。
(3)垃圾回收机制的调用,是由系统决定的,也可以通过System.gc()主动触发垃圾回收机制。
(4)finalize()与C++中的析构函数不是对应的。C++中的析构函数调用的时机是确定的(对象离开作用域或delete掉),但Java中的finalize的调用具有不确定性。
(5)一般避免使用它,可能出现的情况是在我们耗尽资源之前,gc却仍未触发。不建议用finalize方法完成“非内存资源”的清理工作。但建议用于本地对象清理。

多态

● 小栗子引出多态
Master类中有一个feed方法,可以完成主人给动物为食的信息。

public class Rose {
    public static void main(String[] args){
        Master p = new Master("小耶", 20);
        Dog dog = new Dog("狗总");
        Bone ribs = new Bone("大猪排");

        p.feed(dog, ribs);

        Cat cat = new Cat("沐沐");
        Fish fish = new Fish("冰淇凌鱼");
        System.out.println("========");
        p.feed(cat, fish);
    }
}
// 主人类
class Master {
    private String name;
    private int age;

    public Master(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // 主人给小狗喂骨头
    public void feed(Dog dog, Bone bone) {
        System.out.println("主人" + name + "给" + dog.getName() + "吃" + bone.getFood());
    }
    // 主人给小猫喂鱼
    public void feed(Cat cat, Fish fish) {
        System.out.println("主人" + name + "给" + cat.getName() + "吃" + fish.getFood());
    }
}
// 食物类
class Food {
    private String food;

    public Food(String food) {
        this.food = food;

    }

    public String setFood(String food) {
        return food;
    }

    public String getFood() {
        return food;
    }
}
// 鱼类,食物子类
class Fish extends Food {
    public Fish(String food) {
        super(food);
    }
}
// 骨头类,食物子类
class Bone extends Food {
    public Bone(String food) {
        super(food);
    }
}
// 动物类
class Animal {
    private String name;

    public Animal(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
// 猫类
class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }
}
// 狗类
class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }
}


主人小耶给狗总吃大猪排
========
主人小耶给沐沐吃冰淇凌鱼

当有多种食物与动物时,feed方法就会重载多次,这样就显得很low。多态的使用一节中对Master类的feed()方法作出修改。


多态(polymorphic):方法或对象具有多种形态,是面向对象的第三大特征,多态是建立在封装和继承基础之上的。

对象的多态

一个对象的编译类型和运行类型可以不一致
编译类型在定义对象时就确定了,不能改变
运行类型可以改变
编译类型看定义时 = 号左边,运行类型看 = 号右边
以运行类型为主

public class Rose {
    public static void main(String[] args){
        // 对象多态的特点
        // 父类的一个引用可以指向子类的一个对象,animal编译类型是Animal,运行类型是Dog
        Animal animal = new Dog();
        // animal的运行类型变成了Cat,编译类型仍然是Animal
        animal.cry();   //在执行cry()时,animal的运行类型是Dog,所以cry()就是Dog的cry()

        //编译类型还是Animal,运行类型变成Cat
        animal = new Cat();
        animal.cry();

    }
}

class Animal {
    public void cry() {
        System.out.println("Animal cry() 动物在叫。。。");
    }
}

class Cat extends Animal {
    @Override
    public void cry() {
        //super.cry();
        System.out.println("Cat cry() 小猫喵喵喵。。。");
    }
}

class Dog extends Animal {
    @Override
    public void cry() {
        System.out.println("Dog cry() 小狗汪汪汪。。。");
    }
}



Dog cry() 小狗汪汪汪。。。
Cat cry() 小猫喵喵喵。。。

多态的使用

使用多态的前提两个对象(或类)存在继承关系
对多态开头提的Master类的feed方法做出如下改进。

//使用多态机制统一管理喂食方法
//animal的编译类型是Animal。可以指向(接收)Animal子类的对象
//food的编译类型是Food,可以指向(接收)Food子类的对象
public void feed(Animal animal, Food food) {
   System.out.println("主人" + name + "给" + animal.getName() + "吃" + food.getFood());
}

多态的向上转型

本质父类的引用指向了子类的对象
语法

父类类型  引用名 = new  子类类型();

特点
编译类型看左边,运行类型看右边。
○ 可以调用父类中的所有成员(需遵守访问权限),不能调用子类中特有成>员。
○ 最终运行效果看子类的具体体现。

多态的向下转型

语法

子类类型  引用名 = (子类类型) 父类引用;

只能强转父类的引用,不能强转父类的对象
要求父类的引用必须指向的是当前目标类型的对象
● 即:把指向子类对象的父类引用,转成指向子类对象的子类引用
当向下转型后,可以调用子类类型中所有的成员

public class Rose {
    public static void main(String[] args) {
        //向上转型
        Animal animal = new Cat("小椰", 21, "白");
        //不能调用子类的特有成员。
        //在编译阶段,由编译器决定能调用哪些成员,animal的编译类型是Animal,在Animal中找不到catchMouse方法
        //animal.catchMouse();    //报错了
        //在运行阶段,由于animal的运行类型是Cat,按就近原则,先在Cat中找eat()方法
        animal.eat();

        //向下转型
        Cat cat = (Cat)animal;
        cat.catchMouse();
        
        //要求父类的引用必须指向的是当前目标类型的对象。
        //因为原先的animal指向对象就是Cat:Animal animal = new Cat("小椰", 21, "白");
        //animal的引用cat,其类型也是Cat;Cat cat = (Cat)animal;
        //换句话说原来就是一只猫,现在还是一只猫,是可以的,但不能指猫为狗。
        Dog dog = (Dog)animal; //报错
        dog.fly();
    }
}

class Animal {
    String name;
    int age;

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

    public void sleep() {
        System.out.println("睡");
    }

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

class Cat extends Animal {
    String color;

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

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

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


猫猫在吃
猫猫抓老鼠
Exception in thread "main" java.lang.ClassCastException: Cat cannot be cast to Dog
	at Rose.main(Rose.java:19)

属性重写问题

属性没有重写之说属性的值编译类型方法运行类型
instanceof 比较操作符,用于判断对象的类型(运行类型)是否为某某类型或某某类型的子类型。

package com.xiaoye;

public class Jasmine {
    public static void main(String[] args) {
        // num的编译类型是Base,运行类型是Sub
        Base num = new Sub();
        System.out.println(num.count);
        //num1的编译类型,运行类型都是Sub
        Sub num1 = new Sub();
        System.out.println(num1.count);

        Object obj = new Object();
        System.out.println(num instanceof Base);
        System.out.println(num instanceof Sub);
        System.out.println(obj instanceof Base);
    }
}

class Base {
    int count = 10;
}

class Sub extends Base {
    int count = 20;
}


10
20
true
true
false

动态绑定机制

● java的动态绑定机制
(1)当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
(2)当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用

public class Jasmine {
    public static void main(String[] args) {
        A a = new B();
        System.out.println(a.i);    //10
        System.out.println(a.getI());   //20
        System.out.println(a.sum());    //40
        System.out.println(a.sum1());   //30
    }
}

public class A {
    public int i = 10;

    public int getI() {
        return i;
    }

    public int sum() {
        return getI() + 10;
    }

    public int sum1() {
        return i + 10;
    }
}

public class B extends A{
    public int i = 20;

    public int sum() {
        return i + 20;
    }

    public int getI() {
        return i;
    }

    public int sum1() {
        return i + 10;
    }
}


10
20
40
30

删掉子类B中的sum()和getI()方法

public class Jasmine {
    public static void main(String[] args) {
        A a = new B();
        System.out.println(a.i);    //10->10
        System.out.println(a.getI());   //20->10
        System.out.println(a.sum());    //40->20
        System.out.println(a.sum1());   //30->30
    }
}

public class A {
    public int i = 10;

    public int getI() {
        return i;
    }

    public int sum() {
        return getI() + 10;
    }

    public int sum1() {
        return i + 10;
    }
}

public class B extends A{
    public int i = 20;

    public int sum1() {
        return i + 10;
    }
}


10
10
20
30

多态应用

多态数组

数组的定义类型为父类类型,里面保存的元素的类型为子类类型
● 小栗子05——创建1个Person对象,2个Student对象,2个Teacher对象。统一放在数组中,并调用每个对象的say方法,对Student和Teacher增加study和teach功能。

package com.xiaoye;

public class Jasmine {
    public static void main(String[] args) {
        //创建1个Person对象,2个Student对象,2个Teacher对象
        //统一放在数组中,并调用每个对象的say方法
        Person[] persons =new Person[5];
        persons[0] = new Person("小椰", 20);
        persons[1] = new Student("狗总", 21, 90);
        persons[2] = new Student("飘飘", 20, 79);
        persons[3] = new Teacher("随云", 25, 18000);
        persons[4] = new Teacher("沐沐", 22, 20000);

        for (int i = 0; i < persons.length; i++) {
            System.out.println(persons[i].say());   //会有动态绑定机制
            // persons[i].study()报错,因为persons[i]的编译类型是Person,Person中没有子类的特有方法
            //判断persons[i]的运行类型是否是Student
            if (persons[i] instanceof Student) {
                //运行类型是Student的话,就将编译类型强转成Student,向下转型
                //或((Student)persons[i]).study("数据结构与算法")
                Student student = (Student)persons[i];
                System.out.println(student.study("数据结构与算法"));
            } else if (persons[i] instanceof Teacher) {
                System.out.println(((Teacher)persons[i]).teach("数据库"));
            } else if (persons[i] instanceof Person) {
                System.out.println("非老师/学生");
            } else {
                System.out.println("输入类型有误。。。");
            }
        }

    }
}

public class Person {
    private String name;
    private int age;

    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 String say() {
        return "name=" + name + "\tage=" + age;
    }
}

public class Student extends Person{
    private double score;

    public Student(String name, int age, double score) {
        super(name, age);
        this.score = score;
    }

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }

    @Override
    public String say() {
        return super.say() + "\tscore=" + score;
    }

    //特有方法
    public String study(String book) {
        return "学生" + getName() + "正在学" + book;
    }
}

public class Teacher extends Person{
    private double salary;

    public Teacher(String name, int age, double salary) {
        super(name, age);
        this.salary = salary;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    @Override
    public String say() {
        return super.say() + "\tsalary=" + salary;
    }

    //特有方法
    public String teach(String book) {
        return getName() + "老师" + "正在教" + book;
    }
}


name=小椰	age=20
非老师/学生
name=狗总	age=21	score=90.0
学生狗总正在学数据结构与算法
name=飘飘	age=20	score=79.0
学生飘飘正在学数据结构与算法
name=随云	age=25	salary=18000.0
随云老师正在教数据库
name=沐沐	age=22	salary=20000.0
沐沐老师正在教数据库

多态参数

方法定义的形参类型为父类类型,实参类型允许为子类类型
小栗子06——定义员工类Employee,包含姓名和月工资, 以及计算年工资的方法getAnnual()。普通员工和经理继承了员工类,经理类多了奖金属性bonus和管理方法manage(),普通员工多了work()方法,经理类要求重写计算年薪的方法。测试类中添加一个方法showEmpAnnal(Employee e),实现获取任何员工对象的年薪,并在main方法中调用该方法.测试类中添加一个方法testwork(),如果是普通员工,则调用work()方法,如果是经理,则调用manage()方法。

package com.piaopiao;

public class Stick {
    public static void main(String[] args) {
        Staff mm= new Staff("沐沐", 20000);
        Manager xy = new Manager("小椰", 25000, 200000);
        Stick stick = new Stick();
        stick.showEmployeeAnnual(mm);
        stick.showEmployeeAnnual(xy);

        stick.testWork(mm);
        stick.testWork(xy);

    }
    //获取任何对象的年薪
    public void showEmployeeAnnual(Employee e) {
        System.out.println(e.getAnnual());
    }
    //testWork
    public void testWork(Employee e) {
        if (e instanceof Staff) {
            System.out.println(((Staff) e).work());
        } else if (e instanceof Manager) {
            System.out.println(((Manager) e).manage());
        }

    }
}


240000.0
500000.0
员工沐沐正在干活。。。
经理小椰正在阿巴阿巴。。
package com.piaopiao;

public class Employee {
    private String name;
    private double salary;

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;   //单位:万
    }
    //员工名字
    public String getName() {

        return name;
    }
    //获取月工资
    public double getSalary() {

        return salary;
    }
    //计算年薪
    public double getAnnual() {

        return salary * 12;
    }
}
package com.piaopiao;

public class Staff extends Employee{
    
    public Staff(String name, double salary) {
        super(name, salary);
        //this.overtime = overtime;
    }
    //员工的work()方法
    public String work() {

        return "员工" + getName() + "正在干活。。。";
    }
}

package com.piaopiao;

public class Manager extends Employee{
    private double bonus;   //经理有奖金

    public Manager(String name, double salary, double bonus) {
        super(name, salary);
        this.bonus = bonus;
    }

    @Override
    public double getAnnual() {
        return super.getAnnual() + bonus;
    }

    public String manage() {

        return "经理" + getName() + "正在阿巴阿巴。。";
    }
}

类变量和类方法

类变量

也叫静态变量/静态属性,是独立于方法之外的变量,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,得到的都是相同的值;同样任何一个该类的对象去修改它时,修改的也是同一个变量,用static修饰。

// 1.定义类变量
// 访问修饰符 static 数据类型 变量名
// static 访问修饰符 数据类型 变量名

// 2.访问类变量(静态变量的访问修饰符的访问权限和范围和普通属性一样的)
// 类名.类变量名	或者	对象名.类变量名
public class objectOriented {
    public static void main(String[] args) {
        // 类变量是随着类的加载而创建,即使没有创建对象实例也可以访问
        System.out.println(AA.name);    //沐沐
        
    }
}

class AA {
    public static String name = "沐沐";
}

类变量的使用
(1)当某个类需要所有对象都共享一个变量时,就可以考虑使用类变量(静态/全局变量)。
(2)类变量是在类加载时就初始化了,即使没有创建对象,只要类加载了,就可以使用类变量。
(3)类变量的生命周期是随类加载(类在何时加载)开始,随着类消亡而销毁。
类变量与实例变量(普通属性)的区别
(1)类变量是该类的所有对象共享,其中一个对象将它的值改变,其他对象得到是改变后的值。
(2)实例变量是每个对象独享(私有),某一个对象将其值改变,不影响其他对象的调用。

类方法

也叫静态方法。将类本身作为对象进行操作的方法。用static修饰类中的方法,类方法属于整个类的,所以类方法的方法体中不能有与类的对象有关的内容。

// 定义类方法
// 访问修饰符 static 数据返回类型 方法名(){}
// static 访问修饰符 数据返回类型 方法名(){}

// 类方法的调用
//类名.类方法名 或 对象名.类方法名
public class objectOriented {
    public static void main(String[] args) {

        Student.payFee(1000);
        Student straw = new Student("Straw");
        straw.payFee(2000);

        Student.getFee(); //3000.0
    }
}

class Student {
    private String name;
    private static double fee = 0;

    public Student(String name) {
        this.name = name;
    }

    public static void payFee(double money) {
        Student.fee += money;
    }

    public static void getFee() {
        System.out.println("总费用:" + Student.fee);
    }
}

类方法的使用
(1)当方法中不涉及到任何和对象相关的成员(如与对象有关的关键字this和super),可以将方法设计成静态方法。
(2)类方法(静态方法)中只能访问静态变量或静态方法
(3)普通成员方法既可以访问非静态成员,也可以访问静态成员
(4)静态方法只能被继承,不能被重写。如果子类有和父类相同的静态方法,那么父类的静态方法将会被隐藏,对于子类不可见。也就是说,子类和父类中相同的静态方法是没有关系的方法,他们的行为不具有多态性。 但是父类的静态方法可以通过父类.方法名调用。

main()方法

● 定义:public static void main(String[] args) {}
(1)main()方法由java虚拟机调用。
(2)java虚拟机需要调用类的main()方法,所以main()方法的访问权限必须是public。
(3)java虚拟机在执行main()方法时不需要创建对象,所以main()方法必须是static。
(4)main()方法没有返回值,所以只能是void。
(5) IDEA中动态传入参数:Edit Configurations -> 输入参数Program arguments -> Apply -> OK->运行
(6) 该方法接收String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数。

public class objectOriented {
    public static void main(String[] args) {
        for (int i = 0; i < args.length; i++) {
            System.out.println(String.format("args[%d]", i) + "=" + args[i]);
        }
    }
}

args[0]=22
args[1]=3
args[2]=1
args[3]=4

main()方法

代码块

又称为初始化块,属于类中的成员,类似于方法,将逻辑语句封装在大括号{ }中。但是不同于方法,没有方法名,没有返回值,没有参数,只有方法体,而且不用通过对象或类显示调用,而是加载类时或创建对象时隐式调用。

[修饰符] {
	代码块
};

● 注意

  1. 修饰符可写可不写,但是要写只能是static
  2. 有static修饰的代码块叫做静态代码块,没有修饰符修饰的叫普通代码块。
  3. 最后的分号 ; 可写可不写。
  4. 代码块相当于另外一种形式的构造器,可以做初始化的操作
  5. 如果多个构造器中有重复的语句,可以抽取放入代码块中

代码块使用细节

  1. static代码块:对类进行初始化,随类的加载而执行,且只会执行一次。
  2. 类什么时候被加载:(详细见上节类变量中的链接)
    a. 创建对象实例时(new)
    b. 创建子类对象实例,父类对象也会被加载
    c. 使用类的静态成员(静态变量、静态方法)
  3. 普通代码块在创建对象实例时,会被隐式调用,每创建一次就会被调用一次,如果只使用类的静态成员时,普通代码块并不会被执行。
  4. 静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员。
  5. 构造器的最前面其实隐含了super()调用普通代码块
package com.piaopiao;

public class Demo01 {
    public static void main(String[] args) {
        new B();
        //A的构造器被调用。。。
        //B的普通代码块
        //B的构造器被调用。。。

    }
}

class A {
    public A() {
        System.out.println("A的构造器被调用。。。");
    }
}

class B extends A {

    public B() {
        System.out.println("B的构造器被调用。。。");
    }

    {
        System.out.println("B的普通代码块");
    }
}

创建一个对象时,一个类中的调用顺序如下(优先级高的序号靠前)
(1)静态代码块和静态属性初始化调用的优先级一样,按顺序调用。
(2)普通代码块的和普通属性初始化调用的优先级一样,也按顺序调用。
(3)调用构造方法。

package com.piaopiao;

public class Demo01 {
    public static void main(String[] args) {
        A a = new A();
        // A的静态属性
        // A的静态代码块
        // A的普通属性
        // A的普通代码块
        // A的无参构造器
    }
}

class A {

    public A() {
        System.out.println("A的无参构造器");
    }

    private static int age = setAge();
    private String name = setName();

    //普通代码块
    {
        System.out.println("A的普通代码块");
    }

    //静态代码块
    static {
        System.out.println("A的静态代码块");
    }

    public String setName() {
        System.out.println("A的普通属性");
        return "飘飘";
    }

    public static int setAge() {
        System.out.println("A的静态属性");
        return 100;
    }
}

多个类中的调用顺序如下

  1. 父类的静态代码块与静态属性(优先级一样,谁先定义就先调用谁)
  2. 子类的静态代码块与静态属性(优先级一样)
  3. 父类的普通代码块与普通属性(优先级一样)
  4. 父类的构造方法
  5. 子类的普通代码块与普通属性(优先级一样)
  6. 子类的构造方法

抽象层

抽象类

抽象类
当父类的某些方法需要声明,但又不确定如何实现,可以将其声明称抽象(abstract)方法,那这个类就是抽象类。
抽象类不能实例化对象,其他功能与普通类一样。
● 因为抽象类不能被实例化,所以抽象类必须被继承
● 抽象类中可以没有抽象方法。但抽象方法所在的类一定是抽象类
● 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明成抽象类。
● 抽象方法不能被private、final和static修饰,因为这些关键字和重写相违背。

public class objectOriented {
    public static void main(String[] args) {
        commonEmployee p = new commonEmployee("沐沐","0001",20000);
        p.work();

        Manager m = new Manager("小椰", "0002",1200,50000);
        m.work();
    }
}

abstract class Employee {
    private String name;
    private String id;
    private double salary;

    public Employee(String name, String id, double salary) {
        this.name = name;
        this.id = id;
        this.salary = salary;
    }

    //声明抽象方法work()
    public abstract void work();

    public String getName() {
        return name;
    }

    public double getSalary() {
        return salary;
    }
}

class Manager extends Employee{
    private double bonus;

    public Manager(String name, String id, double salary, double bonus) {
        super(name, id, salary);
        this.bonus = bonus;
    }
    
    @Override
    public void work() {
        System.out.println("经理" + getName() + "正在工作,赚了" + (getSalary() + bonus) + "元");
    }
}

class commonEmployee extends Employee {

    public commonEmployee(String name, String id, double salary) {
        super(name, id, salary);
    }

    @Override
    public void work() {
        System.out.println("普通员工" + getName() + "正在工作,赚了" + getSalary() + "元");
    }
}


普通员工沐沐正在工作,赚了20000.0元
经理小椰正在工作,赚了51200.0

接口

接口
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据具体情况补全其方法体。

//1.接口
[可见度] interface 接口名称 [extends 其他的接口名] {
        // 声明变量
        // 抽象方法
}

//2.实现接口
class 类名 implements 接口名[, 其他接口名...] {
    //本类属性
    //本类方法
    //必须实现接口的抽象方法
}
//3.接口的继承
public interface 子类接口名 extends 父类接口名[, 父类接口名1,...]

使用细节

  1. 接口不能实例化,没有构造器。(在匿名内部类中,只需要在new后实现定义的方法,本质上是类的实例化)
  2. 接口不是被类继承(extends),而是被类实现(implements)。
  3. 接口中所有属性肯定是public static final的。在jdk7.0之前,所有的方法都是抽象方法(接口中抽象方法可以省略abstract关键字),在jdk8.0后可以有静态方法、默认方法,即接口中可以有方法具体实现(需要static或default关键字修饰)。
  4. 接口类型可以用来声明变量,他们可以成为一个空指针,或者被绑定在一个以此接口实现的对象。
  5. 接口中属性访问形式:接口名.属性名。
  6. 接口的修饰符只能是public默认的
  7. 接口不能继承其他类,但是可以继承多个别的接口。

接口vs继承

  1. 当子类继承父类,就自动拥有父类的功能。如果子类需要扩展功能,可以通过实现接口的方式扩展,接口可以看作是对继承的一种补充。
  2. 接口比继承更灵活,继承是满足is-a关系,接口是满足like-a关系。
  3. 接口在一定程度上实现代码解耦。
package com.Interface;

public class Jasmine {
    public static void main(String[] args) {
        littleMonkey p = new littleMonkey("沐沐");
        p.climbing();

        littleMonkey02 pp = new littleMonkey02("小耶");
        pp.climbing();
        pp.swimming();
    }
}

class Monkey {
    private String name;

    public Monkey(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void climbing() {
        System.out.println(getName() + "会上树。");
    }
}

class littleMonkey extends Monkey {

    public littleMonkey(String name) {
        super(name);
    }

    @Override
    public void climbing() {
        super.climbing();
    }
}

interface Fish {
    public void swimming();
}

class littleMonkey02 extends Monkey implements Fish {

    public littleMonkey02(String name) {
        super(name);
    }

    @Override
    public void swimming() {
        System.out.println(getName() + "又学会了游泳。");
    }
}


沐沐会上树。
小耶会上树。
小耶又学会了游泳。

接口的多态

多态参数
接口引用可以指向实现了接口的类的对象实例

多态数组

package com.Interface;

public class Jasmine {
    public static void main(String[] args) {
        //接口的多态体现
        Usb[] usb = new Usb[2];
        usb[0] = new Phone();	//接口类型变量 可以指向 实现了接口的对象实例
        usb[1] = new Camera();
        //继承的多态:
        //父类引用 指向 继承了父类的子类 的对象实例。
        for (Usb value : usb) {
            if (value instanceof Phone) {
                ((Phone) value).call();
            }
            value.work();
        }

    }
}

interface Usb{
    public void work();
}

class Phone implements Usb {

    @Override
    public void work() {
        System.out.println("手机开始接入。");
    }

    public void call() {
        System.out.println("手机还能打电话。");
    }
}

class Camera implements Usb {
    @Override
    public void work() {
        System.out.println("相机开始接入。");
    }
}


手机还能打电话。
手机开始接入。
相机开始接入。

多态传递
如果接口2继承了接口1,而某个类实现了接口2,那么也相当于该类实现了接口1。

package com.Interface;

public class Jasmine {
    public static void main(String[] args) {
        Usb01 u = new Phone();
        Usb02 u2 = new Phone();
        u2.hi();
    }
}
interface Usb01 {
    public void hi();
}
interface Usb02 extends Usb01 {}
class Phone implements Usb02 {
    //因为Usb02继承了Usb01,那么相当于Phone类也实现了Usb01,必须实现Usb01的方法。
    @Override
    public void hi() {
        System.out.println("hi");
    }
}

内部类

一个类的内部又完整的嵌套了另一个类的类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。
● 内部类的最大特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系。

class Outer {	//外部类
   class Inner {	//内部类
   }
}
class Other {
}

● 内部类的分类

  1. 定义在外部类的局部位置
    a. 局部内部类(有类名)
    b. 匿名内部类(没有类名)
  2. 定义在外部类的成员位置
    a. 成员内部类(无static修饰)
    b. 静态内部类(static修饰)

局部内部类

指的是定义在外部类的局部位置上,比如方法中,且有类名。
使用细节

  1. 可以直接访问外部类的所有成员。
  2. 不能添加访问修饰符,因为它的地位就是一个局部变量,局部变量不能使用修饰符,但是可以使用final修饰。
  3. 作用域:仅仅在定义它的方法或代码块中。
  4. 外部类在方法中创建局部内部类的对象。
  5. 外部其他类不能访问局部内部类。
  6. 当外部类和局部内部类的成员重名时,默认遵循就近原则,如果向访问外部类的成员,可以使用外部类名.this.成员名(当该成员是静态的可以省略.this,普通成员要加上,外部类名.this指的是外部类的对象)。
package com.Interface;

public class Jasmine {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.func();
    }
}

class Outer {
    private int n = 666;
    //private static int n = 666;

    private void fun() {
        System.out.println("外部类的fun()方法");
    }

    public void func() {
        //局部内部类定义在外部类的局部位置,通常在方法中
        //2.内部类不能使用修饰符,可以用final修饰
        class Inner {   //局部内部类(本质还是一个类,五大成员也可以有)
            private int n = 777;
            //1.可以访问外部类的所有成员
            public void f1() {
                //6.外部类与内部类成员重名,访问成员遵循就近原则,访问外部
                System.out.println("内部类的n=" + n);
                System.out.println("外部类的n=" + Outer.this.n);
                System.out.println("Outer.this hashcode=" + Outer.this);
                //System.out.println("外部类的n=" + Outer.n);
                fun();
            }
        }

        //4.外部类在方法中创建局部内部类的对象
        Inner inner = new Inner();
        inner.f1();
    }
}

outer hashcode=com.Interface.Outer@1b6d3586
内部类的n=777
外部类的n=666
Outer.this hashcode=com.Interface.Outer@1b6d3586
外部类的fun()方法

匿名内部类

指的是定义在外部类的局部位置上,比如方法中,但是没有类名(有名字但是不告诉俺们。有,但没完全有)。
● 使用细节

  1. 匿名内部类既是一个类的定义,也是一个对象
  2. 可以直接访问外部类的所有成员。
  3. 不能添加访问修饰符,因为它的地位就是一个局部变量。
  4. 作用域:仅仅在定义它的方法或代码块中。
  5. 外部类在方法中创建局部内部类的对象。
  6. 外部其他类不能访问局部内部类。
  7. 当外部类和局部内部类的成员重名时,默认遵循就近原则,如果向访问外部类的成员,可以使用外部类名.this.成员名
  8. 匿名内部类当作实参直接传递,简洁高效。

(1)基于接口的匿名内部类

package com.Interface;

public class Jasmine {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.f1();
    }
}

class Outer {
    private int n = 777;

    public void f1() {
        //基于接口的匿名内部类
        //传统写法:写一个类,实现接口方法,创建对象
        /*
            新写法:接口不能实例化,这里本质还是类的实例化
            a的编译类型:接口A类型
            a的运行类型:匿名内部类xxxx -> Outer$1(程序运行时临时生成的类,编译时找不到)
            底层创建匿名内部类时,就立即创建了Outer$1实例,并把地址返回给a
            匿名内部类只能使用一次,但其创建的对象可以想用几次用几次
            class Outer$1 implements A {
                @Override
                public void cry() {
                    System.out.println("哭唧唧");
                }
            }
         */
        A a = new A() {
            @Override
            public void cry() {
                System.out.println("哭唧唧");
            }
        };

        System.out.println("a的运行类型:" + a.getClass().getName());
        a.cry();
    }
}

interface A {
    public void cry();
}


a的运行类型:com.Interface.Outer$1
哭唧唧

(2)基于类的匿名内部类

package com.Interface;

public class Jasmine {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.f1();
    }
}

class Outer {
    //private int n = 777;

    public void f1() {
        /*
            b的编译类型:类B
            b的运行类型:xxxx -> Outer$1
            class Outer$1 extends B {
                public Outer$1(String name) {
                super(name);
                }
                
            }

         */
        B b = new B("沐沐") {
            @Override
            public void f2() {
                super.f2();
                System.out.println(name + ",再见");
            }
        };

        System.out.println("B对象的运行类型:" + b.getClass().getName());
        b.f2();
        
        /*
        基于抽象类的匿名内部类
         */
        new C() {
            @Override
            void f3() {
                System.out.println("贴贴");
            }
        }.f3();
    }
}

class B {
    public String name;

    public B(String name) {
        this.name = name;
    }

    public void f2() {
        System.out.println(name + ",你好");
    }
}


B对象的运行类型:com.Interface.Outer$1
沐沐,你好
沐沐,再见
贴贴

(3)匿名内部类当作实参直接传递

package com.Interface;

public class Jasmine {
    public static void main(String[] args) {

        cellPhone cellPhone = new cellPhone();
        cellPhone.alarmClock(new Bell() {
            @Override
            public void ring() {
                System.out.println("懒猪起床了");
            }
        });

        cellPhone.alarmClock(new Bell() {
            @Override
            public void ring() {
                System.out.println("小伙伴起床了");
            }
        });
    }
}

interface Bell {
    void ring();
}

class cellPhone {
    public void alarmClock(Bell bell) {
        bell.ring();	//动态绑定
    }
}


懒猪起床了
小伙伴上课了

成员内部类

可以访问外部类的私有成员,作为外部类的一个成员存在,与外部类的属性、方法并列。但是没有static修饰。
● 使用细节

  1. 可以直接访问外部类的所有成员。
  2. 可以添加任意访问修饰符(public、protected、默认、private)。
  3. 作用域:和外部类的成员一样。为整个类体。
  4. 外部类访问内部类要先创建对象;成员内部类可直接访问外部类,如果成员内部类与外部类的属性重名,会遵守就近原则,如果想访问外部类的属性:外部类名.this.属性名。
  5. 外部类其他类访问成员内部类的两种方式。
package com.Interface;

public class Jasmine {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.test();

        //外部其他类访问成员内部类的两种方式
        //1.用外部类的一个对象创建了一个内部类的实例,相当于把new Inner()当作outer的一个成员
        Outer.Inner inner1 = outer.new Inner();
        inner1.outPut();
        //2.在外部类的一个方法中返回内部类对象实例
        Outer.Inner inner2 = outer.getInner();
        inner2.outPut();
    }
}

class Outer {
    private int n = 666;
    //private static int n = 666;

    private void fun() {
        System.out.println("外部类的fun()方法");
    }

    public class Inner {
        private double m = 7.7;

        public void outPut() {
            System.out.println("成员内部类");
        }
    }

    public void test() {
        Inner inner = new Inner();
        inner.outPut();
        System.out.println(inner.m);
    }

    //2.在外部类的一个方法中返回内部类对象实例
    public Inner getInner() {
        return new Inner();
    }
}


成员内部类
7.7
成员内部类
成员内部类

静态内部类

定义在外部类的成员位置,并且有static修饰
使用细节

  1. 可以直接访问外部类的所有静态成员,包括私有的,但是不能直接访问非静态成员。
  2. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
  3. 作用域:和外部类的成员一样。为整个类体。
  4. 外部类访问静态内部类方式:创建对象再访问;
  5. 外部其他类访问静态内部类两种方法。
package com.Interface;

public class Jasmine {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.test();

        //外部其他类访问静态内部类
        //1.类Inner是Outer的静态成员,Outer.Inner就代表该静态内部类。
        Outer.Inner inner1 = new Outer.Inner();
        inner1.outPut();
        //2.在外部类中写个方法(静态/非静态都可以)返回静态内部类对象实例
        Outer.getInner().outPut();
    }
}

class Outer {
    private int n1 = 666;
    private static int n2 = 888;

    private static void fun() {
        System.out.println("沐沐");
    }

    protected static class Inner {
        private int m = 777;

        public void outPut() {
            fun();
            System.out.println(n2);
        }
    }

    public void test() {
        Inner inner =new Inner();
        inner.outPut();
    }

    public static Inner getInner() {
        return new Inner();
    }

}


沐沐
888
沐沐
888
沐沐
888

🚀🚀小白飘飘接着冲~~

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值