Java语法-类和对象之抽象类和接口

1.抽象类

1.1 抽象类的概念

一个类中没有足够的信息来描述一个具体的对象,这样的类就是抽象类

比如:

从图中我们可以看出,只有继承了的类,我们产生的实例,调用的draw方法都是他们本身重写的draw方法,不会调用父类Shape的draw()方法,因此我们可以不管父类里面的draw()方法里面的内容,我们直接让它只有一个"壳子"就行.而由这个壳子组成的类就是抽象类

在打印图形例子中, 我们发现, 父类 Shape 中的 draw 方法好像并没有什么实际工作, 主要的绘制图形都是由 Shape的各种子类的 draw 方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstract method), 包含抽象方法的类我们称为 抽象类(abstract class)

再比如:

1.2 抽象类的语法

抽象类在Java中是被abstract 修饰的,抽象类中被abstract修饰的方法被称为抽象方法,抽象方法不需要给出具体的实现体.

复制代码
// 抽象类:被abstract修饰的类
public abstract class Shape {
    // 抽象方法:被abstract修饰的方法,没有方法体
    abstract public void draw();
    abstract void calcArea();
    // 抽象类也是类,也可以增加普通方法和属性
    public double getArea(){
        return area;
    }
    protected double area; // 面积
}

注意:抽象类也是类,内部可以包含普通方法和属性,甚至构造方法

1.3 抽象类的特性

  1. 抽象类不能直接实例化对象(只能被继承)

  2. 抽象类的方法不能是private

  3. 抽象类的方法不能被final和static修饰,因为抽象方法要被子类重写

  4. 抽象类必须被继承,并且后代要重写父类所有的抽象方法,除非子类也是抽象类

  5. 抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类

  6. 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量

1.4 抽象类的作用

**抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法.**但普通类也能被继承,我们为甚非得用抽象类和抽象方法?

确实如此. 但是使用抽象类相当于多了一重编译器的校验.

使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类了, 使用普通类编译器是不会报错的. 但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题.(因为你如果使用的是父类,那么你所对应的业务就完不成,因为你写的具体对应的业务是在子类,是子类在父类扩充出来的业务,比如重写的构造方法,举个上一节讲过的例子toString,你如果用父类的就只会打印对象的地址,而这个和我们具体要打印出对象的属性值的业务是不同的)

2.接口

2.1 接口的概念

比抽象类更抽象的就是接口了,我们来了解以下接口的概念: 在现实生活中,接口的例子比比皆是,比如:笔记本上的USB口,电源插座等。

电脑的USB口上,可以插:U盘、鼠标、键盘...所有符合USB协议的设备

电源插座插孔上,可以插:电脑、电视机、电饭煲...所有符合规范的设备

通过上述例子可以看出:接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型

2.2 语法规则

我们一般使用interface来定义一个接口

复制代码
public interface 接口名称{
    // 抽象方法
    public abstract void method1(); // public abstract 是固定搭配,可以不写
    public void method2();
    abstract void method3();
    void method4();
// 注意:在接口中上述写法都是抽象方法,跟推荐方式4,代码更简洁
}

接口里面的所有方法都是抽象的,某人是public abstract 类型的,因此可以省去,直接void 方法名()即可.

提示:

  1. 创建接口时, 接口的命名一般以大写字母 I 开头.

  2. 接口的命名一般使用 "形容词" 词性的单词.

  3. 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性

2.3 接口使用

接口作为比抽象类更抽象的一种类,当然不能直接创建实例,而且接口因为太抽象了不能被继承,而是使用另一个关键字:implements,让其他类通过它来实现该接口.(只有接口直接才能使用extends进行功能的拓展 后续讲)

public class 类名 implements 接口名字 {}

注意:子类和父类(包括抽象类)之间是extends 继承关系,类与接口之间是 implements 实现关系

2.4 接口的特性

1.接口是一种引用类型,但是不饿能这姐new接口的对象

2.接口中的每个方法都是public的抽象方法,接口中的方法会被隐式的指定为 public abstract

3.接口的方法是不能在接口中实现的,只能由接口的实现类来实现.

4.重写接口中的方法时,不能使用默认访问权限(因为 public > protectes > default > private)子类的访问权限要大于父类.

  1. 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量

  2. 接口中不能有静态代码块和构造方法

  3. 接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class

  4. 如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类

9. jdk8中:接口中还可以包含default方 public abstract,其他修饰符都会报错

2.4 接口之间的继承和案例

在java中类和类时 单继承的,但是一个类时可以实现多个接口,接口和及接口之间时可以多继承的,这就解决了java的单继承问题.

接口可以继承多个接口,达到复用的效果

复制代码
interface IRunning {
    void run();
} interface ISwimming {
void swim();
} // 两栖的动物, 既能跑, 也能游
interface IAmphibious extends IRunning, ISwimming {
}
class Frog implements IAmphibious {

}

我们可以看出俩栖动物这个接口继承了跑,游泳这个功能.然后再由青蛙这个类,实现俩栖动物这个功能.接口间的继承相当于把多个接口合并在一起.

然后我们再来看一个例子:

复制代码
package Class_Object.接口.demo2;
abstract class Animal {
    public String name;
    public int age;
    public Animal(String name,int age) {
        this.name = name;
        this.age = age;
    }
    public abstract void eat();

}
interface IFly {//TODO 设定某种特定功能的时候用接口
    void fly();
}
interface IRun {
    void run();
}
interface ISwim {
    void swim();

}
class Robot implements IRun {

    @Override
    public void run() {
        System.out.println("机器人正在用 机器腿跑步");
    }
}
class Dog extends Animal implements IRun {//TODO 狗是动物,具备 跑 的功能
    public Dog(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println(this.name + "正在吃狗粮");
    }

    @Override
    public void run() {
        System.out.println(this.name + "正在用四条腿跑");
    }
}
class Frog extends Animal implements IRun,ISwim {
    public Frog(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println(this.name + "正在吃虫子");

    }

    @Override
    public void run() {
        System.out.println(this.name + "正在用俩条腿跳着跑");
    }

    @Override
    public void swim() {
        System.out.println(this.name + "正在蛙泳");
    }
}
class Duck extends Animal implements IRun,ISwim,IFly {
//TODO alt + 回车可以快速生成
    public Duck(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println(this.name + "正在吃鸭粮");

    }

    @Override
    public void fly() {
        System.out.println(this.name + "正在用翅膀飞");
    }

    @Override
    public void run() {
        System.out.println(this.name + "正在摇着屁股跑");

    }

    @Override
    public void swim() {
        System.out.println(this.name + "正在用脚板划着游泳");
    }
}
public class Test {
    public static void fuc(Animal animal) {
        animal.eat();
    }
    public static void run(IRun iRun) {
        iRun.run();
    }
    public static void fly(IFly ifly) {
        ifly.fly();
    }
    public static void swim(ISwim iSwim) {
        iSwim.swim();
    }

    public static void main(String[] args) {
        run(new Dog("小白狗",17));
        run(new Frog("箭毒蛙",7));
        run(new Duck("唐老鸭",12));
        System.out.println("=====");
        fly(new Duck("唐老鸭",12));
        System.out.println("=====");
        run(new Robot());//只要具备跑这个功能都能用
    }
    public static void main1(String[] args) {
        Animal animal = new Dog("小白狗",17);
        Animal animal1 = new Frog("箭毒蛙",7);
        Animal animal2 = new Duck("唐老鸭",12);
        fuc(animal);
        fuc(animal1);
        fuc(animal2);//或者直接在里面new


    }
}

我们来好好理解一下:

复制代码
abstract class Animal {
    public String name;
    public int age;
    public Animal(String name,int age) {
        this.name = name;
        this.age = age;
    }
    public abstract void eat();

}

首先,我们定义了一个抽象类Animal,它有name,age俩个属性,还有一个有俩个参数的构造方法,并且有一个eat的抽象方法.

复制代码
interface IFly {//TODO 设定某种特定功能的时候用接口
    void fly();
}
interface IRun {
    void run();
}
interface ISwim {
    void swim();

}

然后我们定义了三个接口,我们用接口来表示某种特定功能,比如Ifly,IRun,ISwim,表示了飞,跑,游泳三个功能.

复制代码
class Dog extends Animal implements IRun {//TODO 狗是动物,具备 跑 的功能
    public Dog(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println(this.name + "正在吃狗粮");
    }

    @Override
    public void run() {
        System.out.println(this.name + "正在用四条腿跑");
    }
}

然后我们写了一个Dog类,让它继承Animal这个类,并且实现了IRun这个功能,也可以这么理解:狗是一种动物,具备跑的功能.然后因为我们继承了AInimal这个类,我们要重写eat这个方法,我们实现了IRun这个接口,因此我们要重写run这个方法.

后面的青蛙和鸭子就不赘述,只不过多实现了几个功能.

复制代码
public class Test {
    public static void fuc(Animal animal) {
        animal.eat();
    }
    public static void run(IRun iRun) {
        iRun.run();
    }
    public static void fly(IFly ifly) {
        ifly.fly();
    }
    public static void swim(ISwim iSwim) {
        iSwim.swim();
    }

    public static void main(String[] args) {
        run(new Dog("小白狗",17));
        run(new Frog("箭毒蛙",7));
        run(new Duck("唐老鸭",12));
        System.out.println("=====");
        fly(new Duck("唐老鸭",12));
        System.out.println("=====");
        run(new Robot());//只要具备跑这个功能都能用
    }
    public static void main1(String[] args) {
        Animal animal = new Dog("小白狗",17);
        Animal animal1 = new Frog("箭毒蛙",7);
        Animal animal2 = new Duck("唐老鸭",12);
        fuc(animal);
        fuc(animal1);
        fuc(animal2);//或者直接在里面new


    }

我们先来看main1,我们使用向上转型,生成了三种动物实例,然后调用func方法,在fuc里面调用各自重写的eat方法,在这个过程中发生了动态绑定,上述整个过程叫做多态.

运行结果:

然后我们再来看main方法.我们先在main外面写了三个静态方法,分别用来调用run,fly,swim方法,然后,我们在main内部调用这些动物的run方法

这个是运行结果:

注意:关于为什么我们还要在main外面写run,fly,swim方法来调用子类的方法,是因为我们是用的向上转型,使用的方法不能是父类也就是(Animal)没有的方法,而那些方法都是子类重写的Ifly()等接口的方法.

2.4 比较类和比较接口

我们一般比较普通类型的变量直接用大于号小于号比较即可

复制代码
    public static void main1(String[] args) {
        int a = 10;
        int b = 20;
        System.out.println(a > b);
    }

但是我们比较引用类型就不行了

复制代码
  public static void main(String[] args) {
        Student student1 = new Student("张三",12);
        Student student2 = new Student("李三",12);
        System.out.println(student2==student1);
}
//运行结果是false

注意: 此处我们比较的是地址,我们创建实例的地址肯定是不同的.那么,我们该怎么比较呢?根据什么比较?年龄?姓名?

此时我们引入一个接口:Compareable,这个表示是可比较的.

然后相应的,我们要重写compareTo方法.

这个是重写的方法

这个是整体的代码,我们根据的是年龄来比较

复制代码
package 克隆;

class Student  implements Comparable<Student>{//这个是泛型,规定比较的类型
    String name;
    int age;

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

    @Override
    public int compareTo(Student o) {
        if(this.age > o.age) {
            return 1;
        } else if (this.age == o.age) {
            return 0;
        }else {
            return -1;//TODO 实现了Compareable接口,重写了compareTo方法
            //但是根据姓名比较呢?要大改所以不合适
       }

    }

}

    public class Test {
    public static void main(String[] args) {
        Student student = new Student("小白",17);
        Student student1 = new Student("小黑",17);
        System.out.println(student1.compareTo(student));

    }
}

我们重写compareTo方法来进行比较,这个复用效果比较差,如果我们根据姓名来比较,就要重新修改这个方法,过于麻烦.

因此我们引入了比较器,就是把单独要比较的东西重新写一个类

我们根据年龄和姓名分别写一个比较器

复制代码
class AgeComparator implements Comparator<Student> {

    @Override
    public int compare(Student o1, Student o2) {
        return o1.age - o2.age;
    }
}
//根据name构造出来的比较器
class NameComparator implements Comparator<Student> {

    @Override
    public int compare(Student o1,Student o2) {
        return o1.name.compareTo(o2.name);
    }
}

这里面使用的compareTo是String是实现了Compareable接口,重写了的方法

是正的就是o1>o2,为0就是相等,是负的就是o1<o2

整体代码:

复制代码
package 克隆;

import java.util.Comparator;

class Student {//这个是泛型,规定比较的类型
    String name;
    int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
//根据age构造出来的比较器
class AgeComparator implements Comparator<Student> {

    @Override
    public int compare(Student o1, Student o2) {
        return o1.age - o2.age;
    }
}
//根据name构造出来的比较器
class NameComparator implements Comparator<Student> {

    @Override
    public int compare(Student o1,Student o2) {
        return o1.name.compareTo(o2.name);
    }
}

    public class Test {
    public static void main(String[] args) {
        Student student = new Student("小黑",17);
        Student student1 = new Student("小黑",17);
        NameComparator nameComparator = new NameComparator();
        AgeComparator ageComparator = new AgeComparator();
        System.out.println(nameComparator.compare(student1, student));
        System.out.println(ageComparator.compare(student1, student));

    }
}
//运行结果
0
0

这种方式就比较的灵活,只需要传入俩个要比较的对象即可,而刚刚实现Compareable接口,重写Compareto方法的方式对类的侵入性比较强,一旦那么写好了,以后就只能用那种方式来进行比较了

比较的应用:自定义类型的排序问题

复制代码
package 比较;

import java.util.Arrays;
import java.util.Comparator;

class Student implements Comparable<Student>{//这个是泛型,规定比较的类型
    String name;
    int age;

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


    @Override
    public int compareTo(Student o) {
        System.out.println("调用了重写的compareTo方法");
        return this.age - o.age;
    }

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

    public static void main(String[] args) {

        Student[] students = new Student[3];
        students[0] = new Student("zs",15);
        students[1] = new Student("ls",13);
        students[2] = new Student("wxw",14);
        System.out.println("排序前:"+Arrays.toString(students));
        Arrays.sort(students);//这个sort里面会调用我们自己写的compareTo方法
        System.out.println("排序后:"+Arrays.toString(students));


    }
}
//运行结果:
排序前:[Student{name='zs', age=15}, Student{name='ls', age=13}, Student{name='wxw', age=14}]
调用了重写的compareTo方法
调用了重写的compareTo方法
调用了重写的compareTo方法
调用了重写的compareTo方法
排序后:[Student{name='ls', age=13}, Student{name='wxw', age=14}, Student{name='zs', age=15}]
复制代码
    Arrays.sort(students);//这个sort里面会调用我们自己写的compareTo方法,根据年龄来排序(从小到达进行排序)

但是我们又回到了原先的问题,如果我们也要根据年龄来比较呢?

根据这个图,我们可以看见sort里面的参数是有比较器的,那么我们又可以像之前用比较器来进行比较.

复制代码
package 比较;

import java.util.Arrays;
import java.util.Comparator;

class Student implements Comparable<Student>{//这个是泛型,规定比较的类型
    String name;
    int age;

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


    @Override
    public int compareTo(Student o) {
        System.out.println("调用了重写的compareTo方法");
        return this.age - o.age;
    }

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

class AgeComparator implements Comparator<Student> {

    @Override
    public int compare(Student o1, Student o2) {
        return o1.age - o2.age;
    }
}
class NameComparator implements Comparator<Student> {

    @Override
    public int compare(Student o1, Student o2) {
        return o1.name.compareTo(o2.name);
    }
}


    public static void main(String[] args) {

        Student[] students = new Student[3];
        students[0] = new Student("zs",15);
        students[1] = new Student("ls",13);
        students[2] = new Student("wxw",14);
        AgeComparator ageComparator = new AgeComparator();
        NameComparator nameComparator = new NameComparator();
        System.out.println("比较前"+Arrays.toString(students));
        Arrays.sort(students,nameComparator);//在sort里面根据name比较器来进行比较和排序
        System.out.println("比较后"+Arrays.toString(students));



    }
}

此时我们可以实现一个自己的sort,我们用冒泡排序的方法来写

复制代码
   public static void bubbleSort(Comparable[] comparables) {
            for (int i = 0; i < comparables.length-1; i++) {
                for (int j = 0; j < comparables.length-1-i; j++) {
                    if(comparables[j].compareTo(comparables[j+1]) > 0) {
                        Comparable temp = comparables[j];//此时,重写的compareTo方法其实是比较的age
                        comparables[j] = comparables[j+1];
                        comparables[j+1] = temp;
                    }
                }
            }
        }

这个是整体代码:

复制代码
package 比较;

import java.util.Arrays;
import java.util.Comparator;

class Student implements Comparable<Student>{//这个是泛型,规定比较的类型
    String name;
    int age;

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


    @Override
    public int compareTo(Student o) {
        System.out.println("调用了重写的compareTo方法");
        return this.age - o.age;
    }

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

class AgeComparator implements Comparator<Student> {

    @Override
    public int compare(Student o1, Student o2) {
        return o1.age - o2.age;
    }
}
class NameComparator implements Comparator<Student> {

    @Override
    public int compare(Student o1, Student o2) {
        return o1.name.compareTo(o2.name);
    }
}

    public class Test {
        public static void bubbleSort(Comparable[] comparables) {
            for (int i = 0; i < comparables.length-1; i++) {
                for (int j = 0; j < comparables.length-1-i; j++) {
                    if(comparables[j].compareTo(comparables[j+1]) > 0) {
                        Comparable temp = comparables[j];//此时,重写的compareTo方法其实是比较的age
                        comparables[j] = comparables[j+1];
                        comparables[j+1] = temp;
                    }
                }
            }
        }
    public static void main(String[] args) {

        Student[] students = new Student[3];
        students[0] = new Student("zs",15);
        students[1] = new Student("ls",13);
        students[2] = new Student("wxw",14);
        System.out.println("排序前=========");
        System.out.println(Arrays.toString(students));
        bubbleSort(students);
        System.out.println("排序后=========");
        System.out.println(Arrays.toString(students));

    }
}

注意:我们实现了什么接口,用该接口来向上转型,就只能调用重写的方法,不能调用实现了这个接口的类特有的方法,同理,继承也是一样,我们通过父类的引用来new子类对象,我们就不能调用这个子类特有的方法.

2.5 克隆

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

浅拷贝: 对象里面有一个对象,但是实例共用一个对象

这个是整体代码:

复制代码
package 拷贝;
class Money {
    public double m =19.9;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
class Person implements Cloneable{//要克隆的话一定要实现这个接口,表面当前类是可以被克隆的
    public String name;
    public int age;
    Money money = new Money();


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

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


    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person = new Person("小宝",13);
        Person person1 = (Person) person.clone();//我们此时return 的是object类型,我们此时是另外申请了一片内存空间把person里面的值塞进新的对象里面去
        System.out.println(person1.toString());
        System.out.println(person.toString());
        System.out.println(person.money.m);
        System.out.println(person1.money.m);
        System.out.println("=======");
        person.money.m = 99;
        System.out.println(person.money.m);
        System.out.println(person1.money.m);

    }
}
//结果
Person{name='小宝', age=13}
Person{name='小宝', age=13}
19.9
19.9
=======
99.0
99.0

我们要实现Clonable接口,还得重写clone()方法

注意:

  1. 我们实现了一个Money类,并且在Person类里面创建了Money对象

2.Person person1 = (Person) person.clone();这个强转是因为clone()这个方法发返回值是Object类型的方法,所有我们需要向下强转成Person类

3.关于 person.money.m = 99;

由图可知我们共用的是一个money对象的m值,因此修改了这里面的值,不管是person还是person2都会受影响.因为我们并没有也把money对象也拷贝一份.

深拷贝: 就是把上述图里面的Money也拷贝一份,每个对象有自己独立的Money.

这个是整体代码:

复制代码
package 拷贝;
class Money implements Cloneable {//1.实现深拷贝,要让在类里面生成实例的类也要实现Cloneable接口
    public double m =19.9;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
class Person implements Cloneable{//要克隆的话一定要实现这个接口,表面当前类是可以被克隆的
    public String name;
    public int age;
    Money money = new Money();


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

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


    @Override
    protected Object clone() throws CloneNotSupportedException {
        //2.克隆person的时候也要克隆money
        //先克隆一份Person对象
        Person tmp = (Person) super.clone();
        //把当前对象this所指向的money克隆一份
        //最后把tmp里面的money指向克隆出来的money
        tmp.money =  (Money) tmp.money.clone();
        //最后返回tmp
        return tmp;
    }
}
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person("小白",19);
        Person person2 = (Person)person1.clone();//person2接受了temp的值
        System.out.println("person1 "+person1.money.m);
        System.out.println("person2 "+person2.money.m);
        System.out.println("=============");
        person1.money.m = 99.9;
        System.out.println("person1 "+person1.money.m);
        System.out.println("person2 "+person2.money.m);
    }

}
//运行结果:
person1 19.9
person2 19.9
=============
person1 99.9
person2 19.9

从运行结果可看出,每个person对象都有了自己独立的Money

首先:我们要再刚刚浅拷贝的基础上先让Money类实现Cloneable接口,并且重写clone方法

然后 我们要重写Person的clone()方法,也就是在克隆person的同时也要把money克隆一份,我们先把person克隆一份,用temp这个容器装起来,(记得向下转型)然后我们要把当前对象所指向的money也克隆一份,然后让克隆出来的temp里面的money保存克隆出来的新的money对象的地址,最后返回tmp对象.

以下是示意图:

2.6 抽象类和接口的区别:

3.内部类

emm就是一个定义在方法或者类里面的类,有点整体里面包含另一个整体的意思.

3.1 实例内部类

就是在一个类里面再创建另一个类

我们直接看代码:

复制代码
//TODO 实例内部类
class OterClass {
    public int data1 = 1;
    public static int data2 = 2;
    private int data3 = 3;
    class InnerClass {
        OterClass oterClass = new OterClass();//在内部类里面也可以创建外部类的实例
        public int data1 = 111;//优先访问自己的
        public int data4 = 4;
//        public static int data5 = 5;
        public  static final int data5 = 5;//编译的时候就确定
        private int data6 = 6;
        public void test(){
            System.out.println("InnerClass::test()" + this.data1);//自己的this
            System.out.println("InnerClass::test()" + OterClass.this.data1);//访问外部的同名变量,外部类的this
            System.out.println("InnerClass::test()" + data2);
            System.out.println("InnerClass::test()" + data3);
            System.out.println("InnerClass::test()" + data4);
            System.out.println("InnerClass::test()" + data5);
            System.out.println("InnerClass::test()" + data6);
        }

    }
    public void test() {
        InnerClass innerClass = new InnerClass();
        System.out.println(innerClass.data1);//直接创建内部类的实例来访问内部类的成员
        System.out.println("OuterClass::test()");

    }
}
public class Test {
    public static void main(String[] args) {
        //如何实例化实例内部类的对象呢?通过外部类.内部类,通过外部类的引用来调用内部类
        OterClass oterClass = new OterClass();
        OterClass.InnerClass innerClass = oterClass.new InnerClass();
        innerClass.test();

    }
}

注意:

  1. 外部类中的任何成员都可以在实例内部类方法中直接访问

  2. 实例内部类所处的位置与外部类成员位置相同,因此也受public、private等访问限定符的约束

  3. 在实例内部类方法中访问同名的成员时,优先访问自己的,如果要访问外部类同名的成员,必须:外部类名称.this.同名成员 来访问

  4. 实例内部类对象必须在先有外部类对象前提下才能创建

**5. 实例内部类的非静态方法中包含了一个指向外部类对象的引用(**这意味着当你创建一个实例内部类的对象时,这个内部类的对象会自动关联到创建它的外部类对象。这种机制允许实例内部类访问外部类的所有成员,包括私有成员)

  1. 外部类中,不能直接访问实例内部类中的成员,如果要访问必须先要创建内部类的对象

创建实例内部类的时候,我们先创建外部类的对象,

再: 外部类名字.内部类名字 内部类对象名 = 外部类对象名.new 内部类对象名();

3.2 静态内部类

被static修饰的内部成员类,就是在刚刚实例内部类的基础前面加上static即可,它的特点就是,不需要依靠外部类来创建自己(不需要外部类的引用).

直接看代码:

复制代码
//TODO 静态内部类:不需要外部类的引用
class Out{
    public int data1 = 1;
    public static int data2 = 2;
    private int data3 = 3;
    static class InnerClass {
        public int data4 = 4;
        public static int data5 = 5;
        private int data6 = 6;
        public void test() {
            Out out = new Out();
            System.out.println(out.data1);//可以通过这种方式访问外部的非静态的变量或者方法
            System.out.println("InnerClass::test()");//自己的this
//            System.out.println(data1);外部非静态都不能访问
            System.out.println(data2);
//            System.out.println(data3);
            System.out.println(data4);
            System.out.println(data5);
            System.out.println(data6);

        }
    }
    public void test() {
        System.out.println("out::test()");
    }

}
public class Test {
   public static void main(String[] args) {
        //TODO 静态内部类
        Out.InnerClass innerClass = new Out.InnerClass();
        innerClass.test();
    }
}
//结果:
1
InnerClass::test()
2
4
5
6

我们通过 **外部类名字.内部类名字 内部类对象名 = new 外部类名.内部类名();**就可以创建静态内部类,这样就不需要外部类的引用了.

注意: 在静态内部类中只能访问外部类中的静态成员

如果想访问外部成员的非静态成员可以通过外部类名.的方式来访问

3.3 局部内部类

定义在外部类的方法体或者{}中,该种内部类只能在其定义的位置使用,一般使用的非常少,此处简单了解下语法格式。

直接上代码:

复制代码
public class OutClass {
    int a = 10;
    public void method(){
        int b = 10;
// 局部内部类:定义在方法体内部
// 不能被public、static等访问限定符修饰
        class InnerClass{
            public void methodInnerClass(){
                System.out.println(a);
                System.out.println(b);
            }
        } // 只能在该方法体内部使用,其他位置都不能用
        InnerClass innerClass = new InnerClass();
        innerClass.methodInnerClass();
    }
    public static void main(String[] args) {
// OutClass.InnerClass innerClass = null; 编译失败
    }
}

注意:

  1. 局部内部类只能在所定义的方法体内部使用

  2. 不能被public、static等修饰符修饰

  3. 编译器也有自己独立的字节码文件,命名格式:外部类名字$数字内部类名字.class

  4. 几乎不会使用

3.4 匿名内部类

没有名字的内部类,常用于需要快速创建一个实现了某个接口或者继承了某个类的对象的情况。匿名内部类通常作为参数传递给方法。这个玩意在线程里面常常用

复制代码
//TODO 匿名内部类
interface IA {
    void test();
}
public class Test {
   

    public static void main(String[] args) {
      new IA() {//TODO 匿名内部类,相当于有一个类实现了IA接口,重写了test()方法
            @Override
            public void test() {
                System.out.println("这是重写的接口的方法1!");
            }
        }.test();
      //俩种调用test()的方式
      IA ia = new IA() {//TODO 匿名内部类,相当于有一个类实现了IA接口,重写了test()方法
          @Override
          public void test() {
              System.out.println("这是重写的接口的方法2!");
          }
      };
     ia.test();
    }
}
//运行结果
这是重写的接口的方法1!
这是重写的接口的方法2!

4.Object类

Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object父类。即所有类的对象都可以使用Object的引用进行接收.

范例:使用Object接收所有类的对象

复制代码
class Person{}
class Student{}
public class Test {
    public static void main(String[] args) {
        function(new Person());
        function(new Student());
    }
    public static void function(Object obj) {
        System.out.println(obj);
    }
} //执行结果:
Person@1b6d3586
Student@4554617c

这个是主要重写的方法: 我们先熟悉toString(),equals(),hashcode()

4.1 toString方法

不重写打印的是对象的地址,重写之后打印的是你规定的内容.这个不再赘述

4.2 对象比较equals方法

在Java中,== 进行比较时:a.如果==左右两侧是基本类型变量,比较的是变量中值是否相同b.如果==左右两侧是引用类型变量,比较的是引用变量地址是否相同c.如果要比较对象中内容,必须重写Object中的equals方法,因为equals方法默认也是按照地址比较的,因此如果我们比较的是对象的内容,那么就应该重写equals方法.

复制代码
class Person{
    Public String name;
    Public int age;

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false ;
        } if(
                this == obj) {
            return true ;
        } // 不是Person类对象
        if (!(obj instanceof Person)) {
            return false ;
        }
        
        Person person = (Person) obj ; // 向下转型,比较属性值
        return this.name.equals(person.name) && this.age==person.age ;
    }
}

结论:比较对象中内容是否相同的时候,一定要重写equals方法。

4.3 hashcode方法

这个是toString()方法的源码:

public String toString() {

return getClass().getName() + "@" + Integer.toHexString(hashCode()); }

我们可以知道hashCode()这个方法在帮我们算一个具体的对象的位置,具体在数据结构说明,然后调用Integer.toHexString()方法,将这个地址以16进制输出。

这个是不重写hashcode()方法的时候的代码

复制代码
class Person {
    public String name;
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
public class TestDemo4 {
    public static void main(String[] args) {
        Person per1 = new Person("xiaobai", 20) ;
        Person per2 = new Person("xiaobai", 20) ;
        System.out.println(per1.hashCode());
        System.out.println(per2.hashCode());
    }
}  //执行结果
460141958
1163157884

注意事项:两个对象的hash值不一样

重写之后:哈希值一样

复制代码
class Person {
    public String name;
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int hashCode() {
       return Objects.hashCode(name,age);
    }
}
 class TestDemo4 {
    public static void main(String[] args) {
        Person per1 = new Person("xiaobai", 20) ;
        Person per2 = new Person("xiaobai", 20) ;
        System.out.println(per1.hashCode());
        System.out.println(per2.hashCode());
    }
}
//结果
-2069661493
-2069661493

注意:

1、hashcode方法用来确定对象在内存中存储的位置是否相同

2、事实上hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。

相关推荐
侠客行03177 小时前
Mybatis连接池实现及池化模式
java·mybatis·源码阅读
蛇皮划水怪7 小时前
深入浅出LangChain4J
java·langchain·llm
灰子学技术9 小时前
go response.Body.close()导致连接异常处理
开发语言·后端·golang
老毛肚9 小时前
MyBatis体系结构与工作原理 上篇
java·mybatis
风流倜傥唐伯虎9 小时前
Spring Boot Jar包生产级启停脚本
java·运维·spring boot
二十雨辰9 小时前
[python]-AI大模型
开发语言·人工智能·python
Yvonne爱编码9 小时前
JAVA数据结构 DAY6-栈和队列
java·开发语言·数据结构·python
Re.不晚9 小时前
JAVA进阶之路——无奖问答挑战1
java·开发语言
你这个代码我看不懂9 小时前
@ConditionalOnProperty不直接使用松绑定规则
java·开发语言
pas1369 小时前
41-parse的实现原理&有限状态机
开发语言·前端·javascript