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)子类的访问权限要大于父类.

        5. 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量
        6. 接口中不能有静态代码块和构造方法

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

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

        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 {@Overridepublic void run() {System.out.println("机器人正在用 机器腿跑步");}
}
class Dog extends Animal implements IRun {//TODO 狗是动物,具备 跑 的功能public Dog(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println(this.name + "正在吃狗粮");}@Overridepublic void run() {System.out.println(this.name + "正在用四条腿跑");}
}
class Frog extends Animal implements IRun,ISwim {public Frog(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println(this.name + "正在吃虫子");}@Overridepublic void run() {System.out.println(this.name + "正在用俩条腿跳着跑");}@Overridepublic 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);}@Overridepublic void eat() {System.out.println(this.name + "正在吃鸭粮");}@Overridepublic void fly() {System.out.println(this.name + "正在用翅膀飞");}@Overridepublic void run() {System.out.println(this.name + "正在摇着屁股跑");}@Overridepublic 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);}@Overridepublic void eat() {System.out.println(this.name + "正在吃狗粮");}@Overridepublic 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;}@Overridepublic 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> {@Overridepublic int compare(Student o1, Student o2) {return o1.age - o2.age;}
}
//根据name构造出来的比较器
class NameComparator implements Comparator<Student> {@Overridepublic 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> {@Overridepublic int compare(Student o1, Student o2) {return o1.age - o2.age;}
}
//根据name构造出来的比较器
class NameComparator implements Comparator<Student> {@Overridepublic 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;}@Overridepublic int compareTo(Student o) {System.out.println("调用了重写的compareTo方法");return this.age - o.age;}@Overridepublic 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;}@Overridepublic int compareTo(Student o) {System.out.println("调用了重写的compareTo方法");return this.age - o.age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}class AgeComparator implements Comparator<Student> {@Overridepublic int compare(Student o1, Student o2) {return o1.age - o2.age;}
}
class NameComparator implements Comparator<Student> {@Overridepublic 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方法其实是比较的agecomparables[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;}@Overridepublic int compareTo(Student o) {System.out.println("调用了重写的compareTo方法");return this.age - o.age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}class AgeComparator implements Comparator<Student> {@Overridepublic int compare(Student o1, Student o2) {return o1.age - o2.age;}
}
class NameComparator implements Comparator<Student> {@Overridepublic 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方法其实是比较的agecomparables[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;@Overrideprotected 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;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}@Overrideprotected 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;@Overrideprotected 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;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}@Overrideprotected Object clone() throws CloneNotSupportedException {//2.克隆person的时候也要克隆money//先克隆一份Person对象Person tmp = (Person) super.clone();//把当前对象this所指向的money克隆一份//最后把tmp里面的money指向克隆出来的moneytmp.money =  (Money) tmp.money.clone();//最后返回tmpreturn 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);//自己的thisSystem.out.println("InnerClass::test()" + OterClass.this.data1);//访问外部的同名变量,外部类的thisSystem.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. 实例内部类的非静态方法中包含了一个指向外部类对象的引用(这意味着当你创建一个实例内部类的对象时,这个内部类的对象会自动关联到创建它的外部类对象。这种机制允许实例内部类访问外部类的所有成员,包括私有成员)

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

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

再: 外部类名字.内部类名字 内部类对象名 =  外部类对象名.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()方法@Overridepublic void test() {System.out.println("这是重写的接口的方法1!");}}.test();//俩种调用test()的方式IA ia = new IA() {//TODO 匿名内部类,相当于有一个类实现了IA接口,重写了test()方法@Overridepublic 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;@Overridepublic 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;}@Overridepublic 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() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1549722.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

第十四届蓝桥杯真题Java c组A.求和(持续更新)

博客主页&#xff1a;音符犹如代码系列专栏&#xff1a;蓝桥杯关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 【问题描述】 求1(含)至 20230408(含)中每个数的和。 【答案提交】 这是一道结…

24年下重庆事业单位考试报名超详细流程

&#x1f388;提交报考申请 考生通过重庆市人力资源和社会保障局官网&#xff08;rlsbj.cq.gov.cn&#xff09;“热点服务”中“人事考试网上报名”栏进行报名。报名时间为2024年8月12日9:00—8月17日9:00。 &#x1f388;网上缴费 资格初审合格后&#xff0c;考生应在2024年8…

flink设置保存点和恢复保存点

增加了hdfs package com.qyt;import org.apache.flink.api.java.functions.KeySelector; import org.apache.flink.api.java.tuple.Tuple2;import org.apache.flink.runtime.state.storage.FileSystemCheckpointStorage;import org.apache.flink.streaming.api.datastream.Dat…

精通推荐算法32:行为序列建模总结

1 行为序列建模总体架构 2 行为序列整体总结 用户行为序列建模是推荐算法中至关重要的一环&#xff0c;也是目前较为核心和前沿的研究方向。其主要分为短序列建模和长序列建模两大方向。短序列建模又主要分为池化和序列化两种方式&#xff0c;其中池化包括Sum-Pooling、Averag…

信道衰落的公式

对于天线&#xff1a; 对于天线的面积计算&#xff1a; 天线的接收功率密度&#xff1a; 天线的接收功率&#xff1a; 移动无线信道&#xff08;I&#xff09; (xidian.edu.cn)https://web.xidian.edu.cn/zma/files/20150710_153736.pdf 更加常用的考虑了额外的信道衰落pathlo…

甘肃辣椒油:舌尖上的热辣诱惑

&#x1f4a5;宝子们&#xff0c;今天必须要给你们安利甘肃食家巷的辣椒油&#x1f336;️&#xff01;✨甘肃辣椒油&#xff0c;那可是有着独特魅力的美食瑰宝&#x1f60d;。它以其鲜艳的色泽、浓郁的香气和醇厚的辣味&#xff0c;瞬间点燃你的味蕾&#x1f525;。&#x1f3…

《Spring Boot应用进阶:打造优雅的错误处理机制与全局异常拦截器》

文章目录 自定义异常类AppException封装业务有关的枚举类AppExceptionCodeMsg全局异常拦截器Handler响应类模板Resp案例展示 || Demo项目结构pom依赖DemoController实际执行结果 Demo案例Git地址 | Gitee 本文主要介绍自己在工作中在处理抛出异常类和封装响应类处理的模板总结。…

【matlab画多纵坐标图像】

一 、什么是多纵坐标图像 多纵坐标图像是一种在同一个坐标系中&#xff0c;使用多个纵坐标轴来表示不同的数据指标的图像。在多纵坐标图中&#xff0c;每个纵坐标轴可以有不同的刻度和单位&#xff0c;用于表示不同的数据范围。这样可以方便地比较不同指标的变化趋势&#xff0…

【C语言】单片机map表详细解析

1、RO Size、RW Size、ROM Size分别是什么 首先将map文件翻到最下面&#xff0c;可以看到 1.1 RO Size&#xff1a;只读段 Code&#xff1a;程序的代码部分&#xff08;也就是 .text 段&#xff09;&#xff0c;它存放了程序的指令和可执行代码。 RO Data&#xff1a;只读…

供应链 | 顶刊POMS论文精读:交易成本经济学(TCE)——供应链效率理论

编者按 供应链效率提升指南&#xff1a;不可不知的TCE理论视角 本文为Production and Operations Management 期刊论文&#xff0c;原文信息&#xff1a; Ketokivi, M., & Mahoney, J. T. (2020). Transaction cost economics as a theory of supply chain efficiency. …

自然资源部最新Nature正刊!!!

2024年8月21日&#xff0c;国际顶级期刊《Nature》发表了自然资源部第二海洋研究所李家彪院士为通讯作者&#xff0c;张涛为第一作者的论文“超慢速扩张加克洋中脊的高变化岩浆增生”。这一成果颠覆了国际海洋学术界半个多世纪以来一直认为的超慢速扩张洋中脊岩浆供给极度贫瘠的…

9.28 Qt界面

#include "widget.h"Widget::Widget(QWidget *parent): QWidget(parent) {this->setWindowTitle("Plane");this->setWindowIcon(QIcon("C:/Users/EDY/Desktop/递送发送.png"));QPushButton *btn1new QPushButton;this->setFixedSize(64…

[SAP ABAP] 锁对象

在SAP中使用锁对象&#xff0c;用于避免在数据库中插入或更改数据时出现不一致的情况 1.创建锁对象 数据准备 学校表(ZDBT_SCH_437) 使用事务码SE11创建锁对象 点击"锁对象"单选按钮&#xff0c;输入以E开头的锁定对象的名称&#xff0c;然后点击创建按钮 锁对象名…

施工现场安全帽监控预警#YOLO视觉 ai视频识别安全帽监测系统

在建筑工地上&#xff0c;安全始终是首要任务。为了提高工地安全&#xff0c;引入了安全帽监控预警系统&#xff0c;这是一项创新技术&#xff0c;利用人工智能和视频识别技术来监测工地上的安全帽佩戴情况。 这个系统的主要工作原理是在工地高危区域门口部署安全帽识别系统&a…

前端使用xlsx-js-style导出Excel,带样式,并处理合并单元格边框显示不全和动态插入表头解决

一、在学习之前&#xff0c;先给出一些学习/下载地址&#xff1a; xlsx-js-style下载地址 https://github.com/gitbrent/xlsx-js-style 或者 https://www.npmjs.com/package/xlsx-js-style SheetJS中文教程&#xff1a; https://xlsx.nodejs.cn/docs/csf/cell 二、先看样…

图文深入理解Oracle Network配置管理(二)

本篇图文深入介绍Oracle Network配置管理。 Oracle网络配置的目的 为了方便对Oracle 数据库进行管理&#xff0c;一般以下情况应该对Oracle进行网络配置。 • 在客户端对服务器端数据库进行管理&#xff08;网络客户端管理&#xff09; • 在一台服务器上管理多个数据库&…

fmql之Linux内核定时器

内容依然来自于正点原子。 Linux内核时间管理 内容包括&#xff1a; 系统频率设置节拍率&#xff1a;高节拍率的优缺点全局变量jiffies绕回的概念&#xff08;溢出&#xff09;API函数&#xff08;处理绕回&#xff09; HZ为每秒的节拍数 Linux内核定时器 内容包括&#xf…

基于python的爱心代码游戏实现 面试最常见问题(源码+内容介绍)

开头附上工作招聘面试必备问题噢~~包括综合面试题、无领导小组面试题资源文件免费&#xff01;全文6000干货。 工作招聘无领导小组面试全攻略最常见面试题&#xff08;第一部分&#xff09;共有17章可用于国企私企合资企业工作招聘面试面试必备心得面试总结资源-CSDN文库https…

【重学 MySQL】四十一、子查询举例与分类

【重学 MySQL】四十一、子查询举例与分类 引入子查询在SELECT子句中引入子查询在FROM子句中引入子查询在WHERE子句中引入子查询注意事项 子查询分类标量子查询列子查询行子查询表子查询 子查询注意事项子查询的位置子查询的返回类型别名的使用性能考虑相关性错误处理逻辑清晰 总…

一文带你读懂分库分表,分片,Sharding的许多概念

一文带你读懂分库分表,分片,Sharding的许多概念 分库是将一个库拆分为多个库&#xff0c;分表就是将一个表拆分为多个表。分库分表有垂直拆分和水平拆分。垂直拆分一般是按照业务将表分到不同的库中&#xff08;此种不在本发的讨论范围&#xff09;。水平拆分是将表的数据拆分…