黑马程序员-JAVA笔记(从面向对象进阶开始)持续更新

一、面向对象进阶

1. static 静态变量

static表示静态,是JAVA中的一个修饰符,可以修饰成员方法,成员变量。

被static修饰的成员变量,叫做静态变量

特点:

  • 被该类所有对象共享
  • 不属于对象,属于类
  • 随着类的加载而加载,优先于对象存在

调用方式:

  • 类名调用(推荐)
  • 对象名调用

内存图展示:

2. static 静态方法和工具类

2.1 静态方法

被static修饰的成员方法,叫做静态方法

特点:

  • 多用在测试类和工具类中
  • Javabean类中很少会用(某些设计模式会用)

调用方式:

  • 类名调用(推荐)
  • 对象名调用

2.2 工具类

定义:帮助我们做一些事情的,但是不描述任何事物的类。

已经学过的另外两种类:

Javabean类:用来描述一类事务的类。比如,Student, Yeacher, Dog等

测试类:用来检查其他类是否书写正确,带有main方法,是程序的入口

工具类的特点:

  • 类名见名知意
  • 私有化构造方法
  • 方法定义为静态

2.3 案例

定义一个数组工具类,两个方法,分别用来格式化打印数组和求数组平均数,代码放在下面:

package a01staticDemo1;

import java.util.StringJoiner;

public class arrayUtil {
    // 私有化构造方法
    // 目的:不让外界创建它的对象
    private arrayUtil() {}

    // 需要定义为静态的,方便调用
    public static String printArr(int[] arr){
        StringJoiner sj = new StringJoiner(",", "[", "]");
        for (int i = 0; i < arr.length; i++) {
            sj.add(arr[i] + "");
        }
        return sj.toString();
    }

    public static double getAvg(double[] arr){
        double avg = 0;
        for (int i = 0; i < arr.length; i++) {
            avg += arr[i];
        }
        return avg / arr.length;
    }
}
package a01staticDemo1;

public class TestDemo {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4};
        String s = arrayUtil.printArr(arr);
        System.out.println(s);

        double[] arr2 = {1.0, 22.4, 43.1, 50.6};
        double avg = arrayUtil.getAvg(arr2);
        System.out.println(avg);

    }
}

3. static注意事项

  • 静态方法只能访问静态变量和静态方法
  • 非静态方法可以访问静态变量或者静态方法,也可以访问非静态的成员变量和非静态的成员方法
  • 静态方法中是没有this关键字

总结:

静态方法中,只能访问静态,不能访问非静态。

非静态方法可以访问所有。
静态方法中没有this关键字

代码理解:

非静态的东西,都是跟对象相关的

静态的都是共享的,跟对象无关

内存理解:

静态:随着类的加载而加载

非静态:跟对象有关

内存图:

4. 重新认识main方法

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello world!");
    }
}
  • public: 被JVM调用,访问权限足够大
  • static: 被JVM调用,不用创建对象,直接类名访问因为main方法是静态的,所以测试类中其他方法也需要是静态的
  • void: 被JVM调用,不需要给JVM返回值
  • main: 一个通用的名称,虽然不是关键字,但是被JVM识别
  • Stringl] args:以前用于接收键盘录入数据的,现在没用

5. 继承

5.1 概述

继承是面向对象三大特征之一,可以让雷根类之间产生子父的关系。

封装:对象代表什么,就得封装对应的数据,并提供数据对应的行为

问题:类太多,属性和行为重复

所以将重复的内容放到第三个类中,即父类

  • Java中提供一个关键字extends,用这个关键字,我们可以让一个类和另一个类建立起继承关系
  • student称为子类(派生类) ,Person称为父类(基类或超类)。
public class Student extends Person{}

使用继承的好处
可以把多个子类中重复的代码抽取到父类中了,提高代码的复用性子类可以在父类的基础上,增加其他的功能,使子类更强大。

什么时候用继承?

当类与类之间,存在相同(共性)的内容,并满足子类是父类的一种就可以考虑使用继承优化代码。

5.2 继承的特点

Java只支持单继承,不支持多继承,但支撑多层继承

就是说一个子类只能继承一个父类,不能同时继承多个父类

为什么不支持多继承?

多层继承:就是可以有孙子类(Java里面叫 直接父类和间接父类)

Java祖宗类 Object

所有没有父类的类默认继承Object

小结:

1.Java只能单继承:一个类只能继承一个直接父类
2.Java不支持多继承、但是支持多层继承。
3.Java中所有的类都直接或者间接继承于object类。

注意:

子类只能访问父类中非私有的成员

5.3 子类到底能继承父类中的那些内容?

首先先看结论

构造方法

一旦能继承,违背了构造方法必须与类名相同的原则。

成员变量

加载自解码文件的时候,会把父类也加载到方法区;

在创建对象的时候,堆中内存一部分是从父类继承的成员变量,还有一部分是自己的成员变量。

只要是成员变量,不管是私有还是非私有,都会被继承,但是私有成员变量不能直接使用。

成员方法

虚方法表(非private、非static、非fianl)

每一层类都有自己的虚方法表

内存分析工具

5.4 继承中:成员变量的访问特点

就近原则:谁离我近,我就用谁

现在局部位置找,本类成员位置找,父类成员位置找,逐级往上

调用:

public class Test01 {
    public static void main(String[] args) {
        Son s = new Son();
        s.show();
    }
}

class Base{
    String name = "base";
}

class Son extends Base{
    String name = "son";
    public void show(){
        String name = "show";
        System.out.println(name); // show
        System.out.println(this.name); // son
        System.out.println(super.name); // base
    }
}

5.5 成员方法的访问特点

就近原则

super调用,直接访问父类

this调用,现在本类找,没有的话再去父类找

方法的重写

当父类的方法不能满足子类现在的需求时,需要进行方法重写

书写格式
在继承体系中,子类出现了和父类中一模一样的方法声明,我们就称子类这个方法是重写的方法


@Override重写注解
1. @Override是放在重写后的方法上,校验子类重写时语法是否正确。
2. 加上注解后如果有红色波浪线,表示语法错误。
3. 建议重写方法都加@Override注解,代码安全,优雅!

class Person{
    public void eat(){
        System.out.println("吃米饭");
    }
    public void drink(){
        System.out.println("喝热水");
    }
}

class OverseasStudent extends Person{
    @Override
    public void eat() {
        System.out.println("吃意面");
    }
    @Override 
    public void drink(){
        System.out.println("喝冷水");
    }

    public void lunch(){
        this.eat();
        this.drink();

        super.eat();
        super.drink();
    }
}
方法重写的本质

覆盖了虚方法表中的方法

创建谁的对象,就去查谁的虚方法表

注意事项和要求

1.重写方法的名称、形参列表必须与父类中的一致。
2.子类重写父类方法时,访问权限子类必须大于等于父类 (暂时了解: 空着不写< protected< public)

3.子类重写父类方法时,返回值类型子类必须小于等于父类
4.建议: 重写的方法尽量和父类保持一致
5.只有被添加到虚方法表中的方法才能被重写

public class Test02 {
    public static void main(String[] args) {
        test01();
    }

    public static void test01(){
        ErHa d1 = new ErHa();
        d1.eat();
        d1.drink();
        d1.LookHome();
        d1.ChaiJia();

        ShaPi d2 = new ShaPi();
        d2.eat();
        d2.drink();
        d2.LookHome();


        TianYuan d3 = new TianYuan();
        d3.eat();
        d3.drink();
        d3.LookHome();

    }
}

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

    public void drink(){
        System.out.println("喝水");
    }

    public void LookHome(){
        System.out.println("看家");
    }
}

class ErHa extends Dog{
    @Override
    public void eat(){
        System.out.println("吃狗粮");
    }

    public void ChaiJia(){
        System.out.println("拆家");
    }
}

class ShaPi extends Dog{
    @Override
    public void eat(){
        System.out.println("吃狗粮, 吃骨头");
    }
}

class TianYuan extends Dog{
    @Override
    public void eat(){
        System.out.println("吃剩饭");
    }
}

总结:

1,继承中成员方法的访问特点
this调用: 就近原则。super调用: 直接找父类
2.什么是方法重写?
在继承体系中,子类出现了和父类中一模一样的方法声明我们就称子类的这个方法是重写的方法
3.方法重写建议加上哪个注解,有什么好处?@Override注解可以校验重写是否正确,同时可读性好
4.重写方法有哪些基本要求?
子类重写的方法尽量跟父类中的方法保持一致只有虚方法表里面的方法可以被重写
5.方法重写的本质?
覆盖虚方法表中的方法

5.6 继承中构造方法的访问特点

  • 父类的构造方法不会被子类继承
  • 子类中所有的构造方法默认先访问父类中的无参构造,再执行自己。

为什么?

  • 子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据
  • 子类初始化之前,一定要调用父类构造方法先完成父类数据空间的初始化。

怎么调用父类构造方法的?

  • 子类构造方法的第一行语句默认都是: super(),不写也存在,且必须在第一行。
  • 如果想调用父类有参构造,必须手动写super进行调用。

public class Test03 {
    public static void main(String[] args) {
        test01();
    }

    public static void test01(){
        Student s1 = new Student("zhangsan", 23);
        System.out.println(s1.name + ", " + s1.age);

        Student s2 = new Student();
    }
}

class Human{
    String name;
    int age;

    public Human() {
        System.out.println("父类的无参构造");
    }

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

class Student extends Human{
    public Student(){
        super();
        System.out.println("子类的无参构造");
    }

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

总结:

1.继承中构造方法的访问特点是什么?

  • 子类不能继承父类的构造方法,但是可以通过super调用
  • 子类构造方法的第一行,有一个默认的super();
  • 默认先访问父类中无参的构造方法,再执行自己。
  • 如果想要方法文父类有参构造,必须手动书写

6. 多态

6.1 介绍

没有继承就没有多态

学生形态 和 人的形态

应用场景

student teacher admin

作为函数参数传递时,可以不用每种类型都写一遍

直接把一个通用角色传递进去 Person P

public void register(Person P){
    p.show();
}

6.2 概述

定义:同类型的对象,表现出的不同形态

表现形式:

父类类型 对象名称 = 子类对象;

前提:

  • 有继承关系
  • 有父类引用指向子类对象
  • 有方法重写
package a01DuoTaiDemo1;

public 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;
    }

    public void show(){
        System.out.println(this.name + ", " + this.age);
    }
}
package a01DuoTaiDemo1;

public class Student extends Person{
    @Override
    public void show(){
        System.out.println("学生的信息为" +
                this.getName() + ", " + this.getAge());
    }

    public Student() {
    }

    public Student(String name, int age) {
        super(name, age);
    }
}
package a01DuoTaiDemo1;

public class Teacher extends Person{
    @Override
    public void show(){
        System.out.println("老师的信息为" +
                this.getName() + ", " + this.getAge());
    }

    public Teacher() {
    }

    public Teacher(String name, int age) {
        super(name, age);
    }
}
package a01DuoTaiDemo1;

public class PersonTest {
    public static void main(String[] args) {
        Person stu = new Student();
        stu.setName("dsa");
        stu.setAge(26);
        Person tea = new Teacher();
        tea.setAge(24);
        tea.setName("ewq");

        showTest(stu);
        showTest(tea);
    }

    public static void showTest(Person p){
        p.show();
    }
}

总结:

1. 什么是多态?

对象的多种形态

2. 多态的前提?

有继承/实现关系、有父类引用指向子类对象、有方法的重写

3. 多态的好处

使用父类型作为参数,可以接受所有子类对象

体现多态的扩展性与便利

6.3 多态调用成员的特点

  • 变量调用:编译看左边,运行也看左边
  • 方法调用:编译看左边,运行看右边
package Polymorphism;

public class TestAnimal {
    public static void main(String[] args) {
        // 创建对象(多态方法)
        Animal a = new Dog();
        // 调用成员变量:编译看左边,运行也看左边
        // javac编译代码的时候,会看左边的父类有没有这个变量
        // 如果有,编译成功,如果没有编译失败
        System.out.println(a.name);

        // 调用成员方法:编译看左边,运行看右边
        // javac编译代码的时候,会看左边的父类有没有这个方法
        // 如果有,编译成功,如果没有编译失败
        // java运行代码的时候,实际上运行的是子类中的方法
        a.show();
    }
}

class Animal{
    String name = "animal";
    public void show(){
        System.out.println("动物show");
    }
}

class Dog extends Animal{
    String name = "dog";
    @Override
    public void show(){
        System.out.println("dog show");
    }
}

class Cat extends Animal{
    String name = "cat";
    @Override
    public void show(){
        System.out.println("cat show");
    }
}

理解:

Animal a = new Animal();

a是animal类型,所以默认都会区Animal这个类中去找

成员变量:在子类的对象中,会把父类的成员变量也继承下来

成员方法:如果子类对方法进行了重写,那么在虚方法表中会把父类的方法进行覆盖。

6.4 多态的优势和弊端

优势:

  • 在多态形势下,右边对象可以实现解耦合,便于扩展和维护
  • 定义方法的时候,使用父类型作为参数,可以接受所有子类对象,体现多态的扩展性与便利。

弊端:

不能调用子类的特有功能

原因:当调用成员方法的时候,编译看左边,运行看右边

所以在编译的时候,会先看父类有没有这个方法,没有的话直接报错

解决方案:变回子类类型就可以了

细节:转换类型的时候不能瞎转

instanceof 判断对象是不是这个类型

package polymorphismDemo2;

public class AnimalTest {
    public static void main(String[] args) {
        Animal a = new Cat();
        a.eat();

//        a.lookHome();

//        Dog d = (Dog)a;
//        d.lookHome();

        // 加条件语句判断
        if(a instanceof Dog d){
            d.lookHome();
        }
        else if(a instanceof Cat c){
            c.grtMouse();
        }
    }
}

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

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

    public void lookHome(){
        System.out.println("狗在看家");
    }
}

class Cat extends Animal{
    @Override
    public void eat(){
        System.out.println("猫在吃猫粮");
    }

    public void grtMouse(){
        System.out.println("猫捉老鼠");
    }
}

引用数据类型的类型转换有几种方式?

自动类型转换、强制类型转换

Person p = new Student(); //自动
Student s = (Student) p; //强制

7. 包和final

7.1 包

什么是包?

包就是文件夹。用来管理各种不同功能的Java类,方便后期代码维护。

包名的规则:公司域名反写+包的作用,需要全部英文小写,见名知意。com.itheima.domain

使用其它类的规则:要使用全类名

注意:

  • 使用同一个包中的类时,不需要导包
  • 使用java.lang包中的类时,不需要导包(String类)
  • 其他情况都需要导包
  • 如果同时使用两个包中的同名类,需要用全类名。

7.2 final

可以修饰:

方法:表明该方法是最终方法,不能被重写

一般来说是这个方法一个规则,不想让别人改变

类:表明该类是最终类,不能被继承

变量:叫做常量,只能被赋值一次

package finalTest;

public class finalTest {
    public static void main(String[] args) {
        Son s = new Son();
        s.show();

        // 常量 不赋值也不行
        final int a = 10;
//        ++a;
        
    }
}

class Base{
    public void show(){
        System.out.println("base show");
    }
}

class Son extends Base{
    @Override
    public void show(){
        System.out.println("Son show");
    }
}

7.3 常量

实际开发中,常量一般作为系统的配置信息,方便维护,提高可读性。

常量的命名规范:

  • 单个单词:全部大写
  • 多个单词:全部大写,单词之间用下划线隔开

细节:

  • final修饰的变量是基本类型:那么变量存储的数据值不能发生改变。
  • final修饰的变量是引用类型: 那么变量存储的地址值不能发生改变,对象内部的可以改变
package finalTest;

public class finalTest {
    public static void main(String[] args) {
        
        // 引用数据类型
        final Base b = new Base("qwe", 12);
        // 不会报错
        b.setName("fds");
        b.setAge(14);

//        b = new Base(); // 报错
        // 数组也一样,里面的值是可以改的,地址值不能改变

    }
}

class Base{
    private String name;
    private int age;

    public Base() {
    }

    public Base(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;
    }

    public void show(){
        System.out.println("base show");
    }
}

字符串不能改变就是因为final。

8. 权限修饰符和代码块

8.1 权限修饰符

权限修饰符:用来控制一个成员能够被访问的范围

可以修饰成员变量,方法,构造方法,内部类

分类:

private < 空着不写 < protected < public

使用规则:

一般只用private和public。

特例:如果方法中的代码是抽取其他方法中共性代码,这个方法一般也私有。(arrayList的grow方法)

8.2 代码块

1. 局部代码块

写在方法内部的,提前结束变量的生命周期,可以节省内存

package codeBlockDemo;

public class codeBlockTest1 {
    public static void main(String[] args) {
        int a = 10;
        System.out.println(a);
    } // 代码执行到这里时,a就从内存中消失了
}

2. 构造代码块

package codeBlockDemo;

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

    // 构造代码块
    // 1. 写在成员位置的代码块
    // 2. 作用:可以把多个构造方法中的重复代码抽取出来
    // 3. 执行时机:在创建本类对象时会先执行构造代码块,在执行构造方法
    {
        System.out.println("开始创建对象了");
    }

    public Student() {
    }

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

这种技术也很少用。不够灵活

如果有第三种构造方法不想执行前两种构造方法的重复部分可以这样:

3. 静态代码块

格式:

static{}

特点:需要static关键字修饰,随着类的加载而加载,并且自动触发、只执行一次

使用场景:再累加载的时候,做一些数据初始化的时候使用

package codeBlockDemo;

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

    // 执行时机
    // 随着类的加载而加载,且只执行一次
    static {
        System.out.println("静态代码块");
    }

    public Student() {
        System.out.println("空参构造");
    }

    public Student(String name, int age) {
        System.out.println("有参构造");
        this.name = name;
        this.age = age;
    }
}

9. 抽象类

抽象方法:将共性的行为 (方法)抽取到父类之后。

由于每一个子类执行的内容是不一样

所以,在父类中不能确定具体的方法体

该方法就可以定义为抽象方法。


抽象类: 如果一个类中存在抽象方法,那么该类就必须声明为抽象类

9.1 抽象类的定义格式

public abstarct class 类名{}

9.2 抽象方法的定义格式

public abstract 返回值类型 方法名(参数列表)

package oopAbstractDemo;

public class AbstractTest {
    public static void main(String[] args) {
        Person stu = new Student();
        stu.work();
        Person tea = new Teacher();
        tea.work();
    }
}

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

class Student extends Person{

    @Override
    public void work(){
        System.out.println("学生在学习");
    }
}

class Teacher extends Person{
    @Override
    public void work(){
        System.out.println("教师在上课");
    }
}

注意事项:

  • 抽象类不能实例化
  • 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
  • 可以有构造方法
  • 抽象类的子类
  •         要么重写抽象类中的所有抽象方法
  •         要么是抽象类

练习:

package AbstractPractice;

public abstract class Animal {
    private String name;
    private int age;

    public Animal() {
    }

    public Animal(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;
    }

    public abstract void eat();

    public abstract void drink();
}
package AbstractPractice;

public class Frog extends Animal{
    public Frog() {
    }

    public Frog(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println(getName() + "在吃虫子");
    }

    @Override
    public void drink(){
        System.out.println(getName() + "在喝水");
    }
}
package AbstractPractice;

public class Dog extends Animal{
    public Dog() {
    }

    public Dog(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println(getName() + "在吃骨头");
    }

    @Override
    public void drink(){
        System.out.println(getName() + "在喝水");
    }
}
package AbstractPractice;

public class Sheep extends Animal{
    public Sheep() {
    }

    public Sheep(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println(getName() + "在吃草");
    }

    @Override
    public void drink(){
        System.out.println(getName() + "在喝水");
    }
}
package AbstractPractice;

public class AbstractTest {
    public static void main(String[] args) {
        Animal frog = new Frog("山羊", 12);
        Animal dog = new Dog("狗", 13);
        Animal sheep = new Sheep("🐏", 14);

        test01(frog);
        test01(dog);
        test01(sheep);
    }

    public static void test01(Animal animal){
        animal.eat();
        animal.drink();
    }
}

9.4 意义

强制子类必须按照这种格式进行书写

小结:

1.抽象类的作用是什么样的?
抽取共性时,无法确定方法体,就把方法定义为抽象的强制让子类按照某种格式重写,抽象方法所在的类,必须是抽象类。
2.抽象类和抽象方法的格式?
public abstract 返回值类型 方法名(参数列表);
public abstract class 类名
3.继承抽象类有哪些要注意?
要么重写抽象类中的所有抽象方法
要么是抽象类

10. 接口

为什么要有接口?

接口就是一种规则,是对行为的抽象

应用:

10.1 如何定义一个接口

定义和使用

  • 接口用关键字interface来定义
  •         public interface 接口名
  • 接口不能实例化
  • 接口和类之间是实现关系,通过implements关键字表示
  •         public class 类名 implements 接口名{}
  • 接口的子类(实现类)
  •         要么重写接口中的所有抽象方法
  •         要么是抽象类
  • 注意1:接口和类的实现关系,可以单实现,也可以多实现。
  •         public class 类名 implements 接口名1,接口名2
  • 注意2:实现类还可以在继承一个类的同时实现多个接口。
  •         public class 类名 extends 父类implements 接口名1,接口名2

演示示例:

package InterfaceDemo;

public abstract class Animal {
    private String name;
    private int age;

    public Animal() {
    }

    public Animal(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;
    }

    public abstract void eat();
}
package InterfaceDemo;

public interface Swim {
    public void swim();
}
package InterfaceDemo;

public class Frog extends Animal implements Swim{
    public Frog() {
    }

    public Frog(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println("兔子再吃胡萝卜");
    }

    @Override
    public void swim(){
        System.out.println("青蛙在蛙泳");
    }
}
package InterfaceDemo;

public class Dog extends Animal implements Swim {
    public Dog() {
    }

    public Dog(String name, int age) {
        super(name, age);
    }

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

    @Override
    public void swim() {
        System.out.println("狗在狗刨");
    }
}
package InterfaceDemo;

public class Rabbit extends Animal{
    public Rabbit() {
    }

    public Rabbit(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println("兔子在吃胡萝卜");
    }
}

10.2 结构中成员的特点

成员变量:

只能是常量·

默认修饰符:public static final

也就是说静态方法可以直接调用,且不能进行修改

构造方法:没有

成员方法

只能是抽象方法

默认修饰符:public abstract

JDK7以前,接口中只能写抽象方法

10.3 接口和类的关系

1. 类和类的关系

继承关系,只能单继承,不能多继承,但是可以多层继承

2. 类和接口的关系

实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口

实现了多个接口的时候,需要重写里面的所有方法

代码示例:

我们定义两个接口,当一个测试类实现这两个接口的时候,需要重写这两个接口的所有方法

当这两个接口有重名方法时,测试类只需重写一次即可。

package interfaceDemo1;

public interface inter1 {
    public abstract void method1();
    public abstract void method2();
    public abstract void method3();
}
package interfaceDemo1;

public interface inter2 {
    public abstract void method4();
    public abstract void method5();
    public abstract void method6();
}
package interfaceDemo1;

public class Test implements inter1, inter2{

    @Override
    public void method1() {

    }

    @Override
    public void method2() {

    }

    @Override
    public void method3() {

    }

    @Override
    public void method4() {

    }

    @Override
    public void method5() {

    }

    @Override
    public void method6() {

    }
}

3. 接口和接口之间的关系

继承关系,可以单继承,也可以多继承

细节:如果实现类实现了最下面的子接口,那么就需要重写所有的抽象方法

11. 接口扩展

11.1 JDK8开始接口中新增的方法

JDK7之前,原有接口中如果想添加新的方法(接口升级),对应的实现接口的类必须重写,否则就会报错。

所以JDK8之后,接口的方法可以有方法体

JDK8以后接口中新增的方法(default)

  • 允许在接口中定义默认方法,需要使用关键字 default 修饰
  • 作用: 解决接口升级的问题

接口中默认方法的定义格式:

  • 格式:public default 返回值类型 方法名(参数列表)[}
  • 范例: public default void show(){}

接口中默认方法的注意事项

  • 默认方法不是抽象方法,所以不强制被重写。但是如果被重写,重写的时候去掉default关键字
  • public可以省略,default不能省略
  • 如果实现了多个接口,多个接口中存在相同名字的默认方法,子类就必须对该方法进行重写
package interfaceDemo3;

public interface inter1 {
    public default void show(){
        System.out.println("接口中的默认show方法");
    }

    public abstract void method();

}

实现类如果要实现inter1接口,则必须重写method方法,show方法可以不重写,不重写也可以调用,默认实现接口中的代码块。

JDK8以后接口中新增的方法(static)
允许在接口中定义定义静态方法,需要用static修饰

接口中静态方法的定义格式

  • 格式: public static 返回值类型 方法名(参数列表)[)
  • 范例: public static void show(){ }

接口中静态方法的注意事项

  • 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用public可以省略,static不能省略
  • 静态方法不能被重写,非要写的话属于是重新定义。因为static private final修饰的不在虚方法表中,不能被重写。

JDK9以后接口中新增的方法(private)

接口中有些函数体只为接口内部的函数提供服务,不想被实现类访问,就出现了private

接口中私有方法的定义格式:
格式1: private 返回值类型 方法名(参数列表)[
范例1: private void show(){}
给默认方法提供服务

格式2:private static 返回值类型 方法名(参数列表)[
范例2: private static void method(){}
给私有方法提供服务

示例代码:

package interfaceDemo3;

public interface inter3 {
    public static void show1(){
        System.out.println("show1方法开始执行");
        show4();
    }

    public default void show2(){
        System.out.println("show2方法开始执行");
        show3();
    }

    private void show3(){
        System.out.println("100行代码");
    }

    private static void show4(){
        System.out.println("100行代码");
    }
}

小结:

1.JDK7以前: 接口中只能定义抽象方法。
2.JDK8:接口中可以定义有方法体的方法。 (默认、静态)
3.JDK9: 接口中可以定义私有方法。
4. 私有方法分为两种:普通的私有方法,静态的私有方法

11.2 接口的应用

1.接口代表规则,是行为的抽象。想要让哪个类拥有一个行为,就让这个类实现对应的接口就可以了
2.当一个方法的参数是接口时,可以传递接口所有实现类的对象,这种方式称之为接口多态

示例代码:

package interfacePolymorphism;

public interface Animal {
    public abstract void sound();
}
package interfacePolymorphism;

public class Dog implements Animal{

    @Override
    public void sound() {
        System.out.println("dog barks");
    }
}
package interfacePolymorphism;

public class Cat implements Animal{

    @Override
    public void sound() {
        System.out.println("cat meows");
    }
}
package interfacePolymorphism;

public class Test {
    public static void main(String[] args) {
        Animal dog = new Dog();
        Animal cat = new Cat();

        makeSound(dog);
        makeSound(cat);
    }

    public static void makeSound(Animal animal){
        animal.sound();
    }
}

在上面的示例中,makeSound方法接受一个Animal接口类型的参数,然后调用传入对象的sound()方法。通过传递不同的实现了Animal接口的类的对象(Dog和Cat),我们可以实现方法的多态性,使得makeSound方法可以处理不同类型的动物对象。

11.3 适配器模式

设计模式 (Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结.
使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。
简单理解: 设计模式就是各种套路

适配器设计模式:解决接口与接口实现类之间的矛盾问题

1.当一个接口中抽象方法过多,但是我只要使用其中一部分的时候,就可以适配器设计模式
2.书写步骤
编写中间类XXXAdapter,实现对应的接口
对接口中的抽象方法进行空实现
让真正的实现类继承中间类,并重写需要用的方法
为了避免其他类创建适配器类的对象,中间的适配器类用abstract进行修饰

代码示例:

接口

package interfaceDemo4;

public interface inter {
    public abstract void method1();
    public abstract void method2();
    public abstract void method3();
    public abstract void method4();
    public abstract void method5();
    public abstract void method6();
    public abstract void method7();
    public abstract void method8();
    public abstract void method9();
    public abstract void method10();
}

中间类:

package interfaceDemo4;

public class TestAdapter implements inter{
    @Override
    public void method1() {

    }

    @Override
    public void method2() {

    }

    @Override
    public void method3() {

    }

    @Override
    public void method4() {

    }

    @Override
    public void method5() {

    }

    @Override
    public void method6() {

    }

    @Override
    public void method7() {

    }

    @Override
    public void method8() {

    }

    @Override
    public void method9() {

    }

    @Override
    public void method10() {

    }
}

实现类:

package interfaceDemo4;

public class MyTest extends TestAdapter{
    // 需要用到哪个方法就重写哪个方法

    @Override
    public void method5() {
        System.out.println("只用方法5");
    }
}

测试:

package interfaceDemo4;

public class Test {
    public static void main(String[] args) {
        MyTest mt = new MyTest();
        mt.method5();
    }
}

12. 内部类

1. 概述

类的五大成员:属性、方法、构造方法、代码块、内部类

在一个类的里面再定义一个类,就叫内部类,外面的就叫外部类

其他无关的类就叫外部其他类

内部类表示的事物是外部类的一部分
内部类单独出现没有任何意义

内部类的访问特点:
内部类可以直接访问外部类的成员,包括私有
外部类要访问内部类的成员,必须创建对象

实际应用:arrayList中就有一个内部类-迭代器

分为4种,前三种了解,匿名内部类要掌握

2. 成员内部类

  • 写在成员位置的,属于外部类的成员
  • 成员内部类可以被一些修饰符所修饰,比如: private,默认,protected,public,static等
  • 在成员内部类里面,IDK16之前不能定义静态变量,IDK 16开始才可以定义静态变量

获取成员内部类对象

方式一:

在外部类中编写方法,对外提供内部类的对象(private修饰内部类时会用)

方式二:

直接创建格式:外部类名.内部类名 对象名 = 外部类对象.内部类对象;

package innerClassDemo1;

public class Test {
    public static void main(String[] args) {
        // 创建内部类对象
        /*Outer.Inner oi = new Outer().new Inner();
        System.out.println(oi.name);*/
        
        Outer o = new Outer();
        o.getInstance();


    }
}

class Outer{
    public class Inner{
        public String name;
    }

    public Inner getInstance(){
        return new Inner();
    }
}

成员内部类如何获取外部类的成员变量

package innerClassDemo2;

public class Outer {
    private int a = 10;
    public class Inner{
        private int a = 20;
        public void show(){
            int a = 30;
            System.out.println(Outer.this.a); // 10
            System.out.println(this.a); // 20
            System.out.println(a); // 30
        }
    }
}

outer.this

总结:

1.内部类的分类?
成员内部类,静态内部类,局部内部类,匿名内部类
2.什么是成员内部类?
写在成员位置的,属于外部类的成员
3.获取成员内部类对象的两种方式?
方式一:当成员内部类被private修饰时
在外部类编写方法,对外提供内部类对象
方式二:当成员内部类被非私有修饰时,直接创建对象
Outer.Inner oi = new Outer().new Inner();
4.外部类成员变量和内部类成员变量重名时,在内部类如何访问?
System.out.println(Outer,this.变量名);

3. 静态内部类和局部内部类

1. 静态内部类

静态内部类只能访问外部类中的静态变量和静态方法, 如果想要访问非静态的需要创建对象

package innerClassDemo2;

public class Car {
    String name;
    static int age;
    
    public static class Inner{
        public static void show(){
            Car c = new Car();
            System.out.println(c.name);
            System.out.println(age);
        }
    }
}

创建静态内部类对象的格式: 外部类名.内部类名 对象名 = new 外部类名.内部类名();

调用非静态方法的格式: 先创建对象,用对象调用

调用静态方法的格式:外部类名.内部类名方法名();

2. 局部内部类

1.将内部类定义在方法里面就叫做局部内部类,类似于方法里面的局部变量(可以用final修饰, 但是public那些不行)
2.外界是无法直接使用,需要在方法内部创建对象并使用。
3.该类可以直接访问外部类的成员,也可以访问方法内的局部变量

4.匿名内部类

匿名内部类本质上就是隐藏了名字的内部类

package innerClassDemo3;

public class Test {
    public static void main(String[] args) {
        method1(
                new Animal(){
                    @Override
                    public void eat() {
                        System.out.println("狗吃东西");
                    }
                }

        );
        
        method2(
                new Inter(){
                    @Override
                    public void swim(){
                        System.out.println("游泳");
                    }
                }
        );
    }

//    public static void test01(){
//        匿名内部类
//        实现关系
//        new Inter(){
//            @Override
//            public void swim(){
//                System.out.println("游泳");
//            }
//        };
//
//        // 继承关系
//        new Animal(){
//            @Override
//            public void eat() {
//                System.out.println("吃东西");
//            }
//        };
//    }

    public static void method1(Animal animal){
        animal.eat();
    }

    public static void method2(Inter i){
        i.swim();
    }


}

总结:

1.什么是匿名内部类?
隐藏了名字的内部类,可以写在成员位置,也可以写在局部位置
2.匿名内部类的格式?
new 类名或者接口名() {重写方法;
3.格式的细节
包含了继承或实现,方法重写,创建对象整体就是一个类的子类对象或者接口的实现类对象
4.使用场景
当方法的参数是接口或者类时以接口为例,可以传递这个接口的实现类对象如果实现类只要使用一次,就可以用匿名内部类简化代码

总觉得不是很好理解, 换个说法:

在Java中,匿名内部类(Anonymous Inner Class)是一种特殊的局部内部类,它没有显式的类名,通常用于创建一个只需要使用一次的简单类

匿名内部类通常用于创建接口的实例或继承一个类,并且通常在方法中作为参数传递。它的语法相对简洁,可以在需要的地方直接定义并实例化一个类,而不必显式地定义一个具体的类。

以下是一个匿名内部类的基本语法示例:

匿名内部类实现接口:

interface Greeting {
    void greet();
}

public class Main {
    public static void main(String[] args) {
        Greeting greeting = new Greeting() {
            public void greet() {
                System.out.println("Hello, World!");
            }
        };
        greeting.greet(); // Output: Hello, World!
    }
}

在这个例子中,Greeting是一个接口,我们创建了一个匿名内部类实现了这个接口,并且在匿名内部类中提供了greet()方法的具体实现。

匿名内部类继承类:

class Animal {
    void sound() {
        System.out.println("Animal makes a sound");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal() {
            void sound() {
                System.out.println("Dog barks");
            }
        };
        animal.sound(); // Output: Dog barks
    }
}

在这个例子中,Animal是一个类,我们创建了一个匿名内部类继承了这个类,并且在匿名内部类中重写了sound()方法的实现。

匿名内部类的使用场景通常是在需要一个临时的、简单的类实例时,避免为此目的专门定义一个新的具体类。这种方式使得代码更加紧凑,但也需要注意,匿名内部类不能被复用,因为它没有显式的类名,只能在定义它的地方使用。

ps: 匿名内部类的作用通常是为了实现一个接口或继承一个类,并且提供具体的实现,而不需要创建一个独立的具体类。由于它通常只在一个特定的上下文中使用,所以将匿名内部类写在方法内部是合适的。

  • 4
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值