JavaSE面向对象进阶

static

介绍

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

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

静态变量

  • 特点:被该类所有对象共享

  • 调用方式:

    类名调用(推荐)

    对象名调用

  • 定义方式:修饰符 static 数据类型 变量名 = 初始值;

老师是这么引入的,创建一个学生对象,

  • 属性:姓名、年龄、性别
  • 行为:学习

这个我们之前学过,那么一起跟着创建

Java Bean类代码如下:

package mystatic.a01staticdemo1;public class Student {private String name;private int age;private String gender;public Student() {}public Student(String name, int age, String gender) {this.name = name;this.age = age;this.gender = gender;}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 String getGender() {return gender;}public void setGender(String gender) {this.gender = gender;}// 行为public void study(){System.out.println(this.name + "正在学习");}public void show(){System.out.println(this.name + "," + this.age + "," + this.gender );}
}

test类代码如下

package mystatic.a01staticdemo1;public class StudentTest {public static void main(String[] args) {// 创建第一个学生对象Student s1 = new Student();s1.setName("张三");s1.setAge(23);s1.setGender("男");s1.study();s1.show();// 创建第二个学生对象Student s2 = new Student();s2.setName("李四");s2.setAge(24);s2.setGender("女");s2.study();s2.show();}
}

执行这段代码之后:

张三正在学习
张三,23,男
李四正在学习
李四,24,女

这些都是符合预期的。

老师又添加了一个数学,教他的老师姓名。

那么添加之后的Javabean类是

package mystatic.a01staticdemo1;public class Student {private String name;private int age;private String gender;public String teacherName;public Student() {}public Student(String name, int age, String gender) {this.name = name;this.age = age;this.gender = gender;}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 String getGender() {return gender;}public void setGender(String gender) {this.gender = gender;}// 行为public void study(){System.out.println(this.name + "正在学习");}public void show(){System.out.println(this.name + "," + this.age + "," + this.gender + "," + this.teacherName);}
}

注意变量teacherName是public,不需要写get和set方法

修改了两处地方,添加了变量teacherName,还有修改show方法。

那么在test类中添加s1.teacherName = "阿玮老师";。执行

张三正在学习
张三,23,男,阿玮老师
李四正在学习
李四,24,女,null

理论上李四,24,女,null中为什么null很好理解了,因为没有s2.teacherName = "阿玮老师";

但是问题来了,一个班的学生老师是共享的,所以李四的李四应该也是阿玮老师。s2.teacherName = "阿玮老师";是可以解决问题的,这里就可以用静态变量来解决。

在刚刚在Student中添加的教师名称变量的代码: public String teacherName;,改成public static String teacherName;

在test类中添加s1.teacherName = "阿玮老师";那么运行代码

张三正在学习
张三,23,男,阿玮老师
李四正在学习
李四,24,女,阿玮老师

虽然只是在s1中定义的变量,但是在s2中也是可以使用、

被static修饰的变量可以让所有对象共享。虽然是在s1中进行的赋值,但是s2也共享teacherName="阿玮老师"

静态变量可以理解成固定死了。每个对象都可以用。

s1.teacherName = “阿玮老师”;还可以写成Student.teacherName = "阿玮老师";直接用对象名.的方式

内存图

修改代码如下:

package mystatic.a01staticdemo1;public class Student {String name;int age;static String teacherName;public void show(){System.out.println(this.name + "," + this.age + "," + teacherName);}
}
package mystatic.a01staticdemo1;public class StudentTest {public static void main(String[] args) {Student.teacherName = "阿玮老师";// 创建第一个学生对象Student s1 = new Student();s1.name = "张三";s1.age = 23;s1.show();// 创建第二个学生对象Student s2 = new Student();s2.show();}
}

分别对应JavaBean类和测试类

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

静态区,存放被static修饰的变量。在jdk8以后,静态区放在堆内存里面。

上图是静态的内存图。默认两个show方法已经完成了进栈出栈。

一开始画内存图我都会有很大段的描述。现在,我觉得不需要很多描述了。易如反掌。这也体会到了进步。

需要注意在执行到Student.teacherName = "阿玮老师";的时候,堆内存中是没有对象的、

随着类加载而加载,优先于对象。

静态方法

  • 特点:

    多用于测试类和工具类;

    JavaBean类中很少用;

  • 调用方式

    类名调用(推荐)

    对象名调用

静态变量是写在JavaBean类里面,一开始我以为静态方法也是这样的。但是根据之前写过的代码

package mystatic.a01staticdemo1;public class Student {String name;int age;static String teacherName;public void show(){System.out.println(this.name + "," + this.age + "," + teacherName);}
}

在JavaBean类中,方法是没有用static修饰的,因此静态方法的意思不是在JavaBean类中描述方法的。

// 检查用户
public static int userExists(ArrayList<User> userList, String username){for (int i = 0; i < userList.size(); i++) {User u = userList.get(i);String uName = u.getUsername();// 找到这个用户if (uName.equals(username)) return i;}return -1;
}

反观写过的测试类,方法是有static的。

静态方法也用于工具类。

  • Javabean类:用来描述一类事物的类。比如:Student,Teacher,Dog,Cat等;
  • 测试类:用来检查其他类是否书写正确,带有main方法的类,是程序的入口;
  • 工具类:不是用来描述一类事物的,而是帮我们做一些事情的类;

又学了一种类。当听到这个名词的时候,我以为我学不懂了。

JavaBean类,我们老早就学过,面向对象,类是蓝图,对象是实例。把奥迪、宝马、大众等等这些都是车的牌子,只不过价格不同,性能不同。都是归根结底他们都是车。在制作的时候都要按照车的框架。因此这就是JavaBean来做的事情。把框架做好。

测试类,用于测试,测试JavaBean类写的对不对。工具类中的工具能不能用。有车的图纸了,那我要开始造车了。在测试类中含有main方法,程序的入口

工具类,帮助我们做一些事情,造车需要很多工具。

工具类的命名应做

  • 类名到见名知意;
  • 私有化构造方法,其目的不让外界创建他的对象
  • 方法定义为静态;
public class ArrUtil {private ArrUtil(){}public static int getMax(...){...}public static int getMin(...){...}public static int getSum(...){...}public static int getAvg(...){...}
}

类名ArrUtil,array是数组,util是工具的意思。那么这个类实现的是与数组相关的工具。 获取最大值、最小值、求和,求平均。

定义数组工具类

需求:

在实际开发中,经常会遇到一些数组使用的工具类;

请按照如下要求编写一个数组的工具类:ArrayUtil

  • 提供一个工具类方法printArr,用于返回整数数组的内容。

    返回的字符串格式如:[10,20,50,34,100 ](只考虑整数数组,且只考虑一维数组)

  • 提供这样一个工具方法getAerage,用于返回平均分。(只考虑浮点型数组,且只考虑一维数组)

  • 定义一个测试类TestDemo,调用该工具类的工具方法,并返回结果。

工具类代码如下:

package mystatic.a02staticdemo2;public class ArrUtil {// 私有化构造方法private ArrUtil(){}// 定义静态方法,方便调用。public static String printArr(int[] arr) {StringBuilder sb = new StringBuilder();sb.append("[");for (int i = 0; i < arr.length; i++) {if (i == arr.length-1) sb.append(arr[i]);else sb.append(arr[i]).append(",");}sb.append("]");return sb.toString();}public static double getAverage(double[] arr) {double sum = 0;for (int i = 0; i < arr.length; i++) {sum += arr[i];}return sum / arr.length;}}

测试类代码如下:

package mystatic.a02staticdemo2;public class TestDemo {public static void main(String[] args) {int[] arr1 = {1, 2, 3, 3, 4};String str = ArrUtil.printArr(arr1);System.out.println(str);double[] arr2 = {1.1, 2.2, 3.3, 4.4, 5.5};double avg = ArrUtil.getAverage(arr2);System.out.println(avg);}
}

代码是没有什么难度的。我以为工具类的出现就是结构化代码。更方便阅读代码。

实际上这些事情之前都是写过的。全都写在了一个文件里面。把他们分开也增加了代码的阅读性。

定义学生工具类

需求:定义一个集合,用于存储3个学生对象。
学生类的属性为:name、age、gender
定义一个工具类,用于获取集合中最大学生的年龄。

JavaBean类:

package mystatic.a03staticdemo3;public class Student {// 学生类的属性为:name、age、genderprivate String name;private int age;private char gender;public Student() {}public Student(String name, int age, char gender) {this.name = name;this.age = age;this.gender = gender;}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 char getGender() {return gender;}public void setGender(char gender) {this.gender = gender;}
}

工具类:

package mystatic.a03staticdemo3;import java.util.ArrayList;public class StudentUntil {private StudentUntil(){}// 定义一个工具类,用于获取集合中最大学生的年龄。public static int getMaxAge(ArrayList<Student> stuList){int stuMaxAge = stuList.get(0).getAge();  // 设最大值是第一个学生for (int i = 1; i < stuList.size(); i++) {int stuAge = stuList.get(i).getAge();if (stuAge > stuMaxAge) stuMaxAge = stuAge;}return stuMaxAge;}
}

测试类:

package mystatic.a03staticdemo3;import java.util.ArrayList;public class Test {public static void main(String[] args) {ArrayList<Student> stulist = new ArrayList<>();Student stu1 = new Student("zhangsan", 24, '男');Student stu2 = new Student("lisi", 25, '男');Student stu3 = new Student("wangwu", 23, '男');stulist.add(stu1);stulist.add(stu2);stulist.add(stu3);int maxAge = StudentUntil.getMaxAge(stulist);System.out.println(maxAge);}
}

这种代码以前使用两个文件写的,现在变成三个文件了。多了一个工具类。而代码的逻辑都是一样的。求最值是在数组里面写过,这次换成了集合。

注意事项

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

跟绕口令似的,说白了静态方法只能访问静态,而非静态方法都可以访问所有;静态方法中没有this。

代码方面理解

JavaBean类:

package mystatic.a04staticdemo4;public class Student {String name;int age;static String teacherName;public void show1() {System.out.println(name + ", " + age + ", " + teacherName);}public static void method(){System.out.println("这是一个静态方法");}
}

里面有一个静态方法和非静态方法。

test类:

package mystatic.a04staticdemo4;public class Test {public static void main(String[] args) {Student.teacherName = "阿玮老师";Student s1 = new Student();s1.name = "zhangsan";s1.age = 23;s1.show1();System.out.println("=============");Student s2 = new Student();s2.name = "lisi";s2.age = 20;s2.show1();}
}

这段代码的运行是:

zhangsan, 23, 阿玮老师
=============
lisi, 20, 阿玮老师Process finished with exit code 0

静态方法中没有this关键字,反过来就是非静态方法中有this关键字,存在于方法的形参中,这个形参不需要我们赋值,虚拟机自动赋值。

this指代调用者本身的地址,意思是在执行 Student s1 = new Student();的时候,内存中会创建对象s1的地址,反而 Student s2 = new Student();会创建s2是地址。

那么s1.show1()的时候,this指代的是s1的地址。s2.show1()也是如此。

public void show1(Student this) {System.out.println("this" + this);System.out.println(name + ", " + age + ", " + teacherName);
}

改动show1方法。也打印S1和S2的地址System.out.println("s1" + s1);和`System.out.println(“s2” + s2);

s1: mystatic.a04staticdemo4.Student@682a0b20
this: mystatic.a04staticdemo4.Student@682a0b20
zhangsan, 23, 阿玮老师
=============
s2mystatic.a04staticdemo4.Student@5b480cf9
this: mystatic.a04staticdemo4.Student@5b480cf9
lisi, 20, 阿玮老师

从输出中可以看出对象地址就等于this

也有了如下代码

public void show1(Student this) {System.out.println("this: " + this);System.out.println(this.name + ", " + this.age + ", " + teacherName);
}

this.成员变量,this表示都对象,那么就是s1的name,s1的age,s2的name,s2的age。

teacherName不能用this,因为是静态变量,共享,不分你的我的

如果在Student类里面继续编写一个show2(),让`show1()调用。

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

show1()里面可以用this调用,this.show2()

public void show1(Student this) {System.out.println("this: " + this);System.out.println(this.name + ", " + this.age + ", " + teacherName);this.show2();
}

this.show2()可以理解为用对象调用show2()

静态方法中没有this,即使手动添加形参this

public static void method(Student this){System.out.println("this: " + this);System.out.println("这是一个静态方法");
}

代码会报错。idea直接标红了。

Java为什么要这么设计,非静态的东西是和对象有关系的,你的就是你的,我的就是我的,他不会共享,就要分得很清。而静态的对象,共享,this是个代词,东西都共享了,也就不用特别指代了。

public static void method(){System.out.println(name);show1();System.out.println("这是一个静态方法");
}

这段代码会报错,静态方法不能调用非静态的内容,不能调用非静态的方法和变量。即使是this.name 或者this.show1()

但是System.out.println(teacherName);是允许的;

public void show1(Student this) {System.out.println("this: " + this);System.out.println(this.name + ", " + this.age + ", " + teacherName);this.show2();method();
}

在非静态方法中调用静态方法,是允许的。

以上就是代码角度,一开始对概念有点晕头转向,像绕口令。通过代码实践。显而易见了。

内存方面理解

  • 静态:随着类加载而加载;、
  • 非静态:跟对象有关;

静态内容和非静态内容的加载顺序不一样。就算是静态变量或方法会优先于非静态变量和方法出现的。创建对象之前肯定会先加载类,在加载类的时候,静态的内容就已经出来了。

不创建对象,那么非静态的内容就不会出现。让已经出现的静态内容找一个没有出现的非静态内容,这个肯定是找不到的。

静态方法不能访问非静态内容,

package mystatic.a04staticdemo4;public class Student {String name;static String teacherName;public void show() {System.out.println(this.name + ", " + teacherName);}public static void method(){System.out.println(name + ", " + teacherName);}
}
package mystatic.a04staticdemo4;public class Test {public static void main(String[] args) {Student.teacherName = "阿玮老师";Student.method();}
}

这段代码是有问题的,因为静态方法method不可以访问非静态变量name,接下来看一下,在内存中为什么不能访问。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

方法区用来加载类,在执行Student.teacherName = "阿玮老师";的时候,Student.class就会加载到方法区。所有的成员变量和成员方法全部加载进来。在jdk以前,不管是静态还是非静态内容都会加载到方法区。jdk7以后,静态变量移到了堆内存。

执行method()方法后。method()入栈,执行method()里的内容。System.out.println(name + ", " + teacherName);就在这个时候,静态变量teacherName可以在静态区里面找到,但是非静态变量name怎么办呢,这个时候,只能报错了、

java: 无法从静态上下文中引用非静态 变量 name

非静态变量又叫实例变量,实例就是对象的意思。因此静态方法不能调用实例变量。

接下来,静态方法不能调用非静态的成员方法。

package mystatic.a04staticdemo4;public class Student {String name;static String teacherName;public static void method(){System.out.println("静态方法" );show();}public void show() {System.out.println(this.name + ", " + teacherName);}
}
package mystatic.a04staticdemo4;public class Test {public static void main(String[] args) {Student.teacherName = "阿玮老师";Student.method();}
}

这段代码还是有问题的,静态方法method()不能调用非静态方法show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

静态方法调用时不需要对象名的,如果在静态方法里面调用非静态方法,那么就是???.show()不知道是谁调用的。

非静态可以访问所有。

package mystatic.a04staticdemo4;public class Student {String name;int age;static String teacherName;public static void method(){System.out.println("静态方法" );}public void show() {System.out.println(this.name + ", " + teacherName);}
}
package mystatic.a04staticdemo4;public class Test {public static void main(String[] args) {Student s1 = new Student();s1.name = "张三";s1.age = 23;s1.show();}
}

这段代码是正确的,也要明白非静态可以访问所有。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这个里面箭头有点乱。但是需要输出的变量都i能找到,因此代码没有问题。

非静态可以访问所有。

ackage mystatic.a04staticdemo4;public class Student {String name;int age;static String teacherName;public static void method(){System.out.println("静态方法" );}public void show() {System.out.println(this.name + ", " + teacherName);method();}
}
package mystatic.a04staticdemo4;public class Test {public static void main(String[] args) {Student s1 = new Student();s1.name = "张三";s1.age = 23;s1.show();}
}

非静态方法调用静态,这段代码是没有问题的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这也是能找到的。

重新认识main方法

public class helloWorld {public static void main(String[] args) {System.out.println("helloWorld");}
}
  • public:被VM调用,访问权限足够大

  • static:被VM调用,不用创建对象,直接类名访问;

    因为main方法是静态的,所以测试类中其他方法也需要是静态的。

  • void:被VM调用,不需要给VM返回值

  • main:一个通用的名称,虽然不是关键字,但是被VM识别

  • String[] args:以前用于接收键盘录入数据的,现在没用

接下来详细说一下String[] args

一眼看去String[] args是main的形参。这个参数默认情况下是没有东西的

public class helloWorld {public static void main(String[] args) {System.out.println(args.length);  // 0}
}

想要让它有输出,需要在idea里面配置。找到【Run/Debug Configurations】界面在【Program arguments】里面输入内容Hello World Java,内容与内容之间用空格隔开。否则就是一个整体

public static void main(String[] args) {System.out.println(args.length); for (int i = 0; i < args.length; i++) {System.out.println(args[i]);}}

​ 接下来这段代码就会有输出内容,内容是

3
Hello
World
Java

继承

继承介绍

从字面理解继承,父母的东西应该就是孩子。在Java中,也是这么一个理。

比如说开发一个校园管理系统。我需要设置两个类。学生类和教师类。

学生类有如下属性:学号、名字、年龄、地址;如下方法:吃饭、睡觉、学习和get/set方法;

教师类有如下属性:工号、名字、年龄、地址;如下方法:吃饭、睡觉、教学和get/set方法

虽然是两种不同的类,但是他们的属性和方法大部分是一样的。如名字、年龄、地址、吃饭、睡觉等

因此可以这么设计,把教师类和学生类共有的内容都放在一个类里面,然后让学生类和教师类继承这个类。

Java中提供一个关键字extends,用这个关键字,我们可以让一个类和另一个类建立起继承关系

public class Student extends Person {}
  • Student称为子类(派生类)
  • Person 称为父类(基类或者超类)
class 父类 {...
}class 子类 extends 父类 {...
}

这是一个完整的格式。

派生类、基类、超类有点复杂。继承的关系大致就是父类、子类。

使用继承的好处

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

继承还是方便,同一个东西你就不用重复去写了。

子类继承父类的一切之后,还可以继续写自己的内容。在父类之上继续开发。

并不是任何时候都会使用继承,应满足如下条件

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

相同的部分可以放到父类去继承,但是不一样的部分就只能是各自了。就好比,把老师的教书方法放到父类,学生也就有了教学这个方法,不现实呀。学生不能有教学的方法。

继承的特点

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

单继承:单,一个,意思是一个子类只有一个父类。不能有多个父类。因此不能多继承。

多层单继承呢,意思,A继承B,而B又继承C。是这么一个关系。在这里又会引出几个概念。

既然是这么个继承,那么C里的方法A也可以用、

  • B是A的直接父类
  • C是A的间接父类

每个类都直接或间接的继承于Object

C的上面会不会还有类呢,如果在代码中没有指明。那么继承于Object

从这里可以看出,面向对象的思维方式很符合实现世界。一个儿子只有一个父亲。儿子、父亲、爷爷的关系就是一个多层单继承。

Java有一个继承体系。儿子不能用他叔叔的东西、叔叔继承于爷爷。虽然爷爷是儿子的间接父类,但是儿子的直接父类是父亲。

练习题

现在有四种动物:布偶猫、中国狸花猫、哈士奇、泰迪。暂时不考虑属性,只要考虑行为。请按照继承的思想特点进行继承体系的设计。

四种动物分别有以下的行为:

  • 布偶猫:吃饭、喝水、抓老鼠
  • 中国狸花猫:吃饭、喝水、抓老鼠
  • 哈士奇:吃饭、喝水、看家、拆家
  • 泰迪:吃饭、喝水、看家、蹭一蹭

这么一堆内容,我本来准备上来直接写代码,但是老师说先画图。先画子类,再画父类。把子类共有的特性抽取到父类。

Ragdoll
LiHua
Husky
breakHome() : null
Teddy
touch() : null
Cat
catchMouse() : null
Dog
lookHome() : null
Animal
eat() : null
drink() : null

上面的图就是我们写的代码,在写代码的时候也应该注意一点:先写父类,后写子类。

代码如下

  • Animal.java

    package myextends.a01exendsdemo01;public class Animal {public void eat(){System.out.println("正在吃饭...");}public void drink(){System.out.println("正在喝水...");}
    }
  • Cat.java

    package myextends.a01exendsdemo01;public class Cat extends Animal{public void catchMouse(){System.out.println("正在抓老鼠...");}
    }
  • Dog.java

    package myextends.a01exendsdemo01;public class Dog extends Animal{public static void bookHome(){System.out.println("正在看家...");}
    }
  • Ragdoll.java

    package myextends.a01exendsdemo01;public class Ragdoll extends Cat{
    }
  • LiHua.java

    package myextends.a01exendsdemo01;public class LiHua extends Cat{
    }
  • Husky.java

    package myextends.a01exendsdemo01;public class Husky extends Dog {public void breakHome(){System.out.println("正在拆家...");}
    }
  • Teddy

    package myextends.a01exendsdemo01;public class Teddy extends Dog{public void touch(){System.out.println("正在蹭一蹭...");}
    }
  • Test.java

    package myextends.a01exendsdemo01;public class Test {public static void main(String[] args) {Ragdoll rd = new Ragdoll();rd.eat();rd.drink();rd.catchMouse();System.out.println("============");Husky h = new Husky();h.eat();h.breakHome();h.breakHome();}
    }

以上就是完整的代码。

父类的内容可以不继承。有些东西我不想给你用。因此权限可以设置成private

package myextends.a01exendsdemo01;public class Animal {private void eat(){System.out.println("正在吃饭...");}private void drink(){System.out.println("正在喝水...");}
}

这样的话,父类的etadrink方法子类就用不了。

子类继承父类哪些内容

父类的所有内容并不会完全继承给子类的,

子类不能继承父类的构造方法。

值得注意的是子类可以继承父类的私有成员(成员变量,方法),只是子类无法直接访问而已,可以通过getter/setter方法访问父类的private成员变量。

这段话是从老师的笔记上摘抄下来的,我的理解是父类中有的内容子类是不会继承的,什么情况下不会继承,我没有听明白。

  • 父类的构造方法无论是非私有还是private,子类都不可以继承。
  • 父类的成员变量是非私有还是private,子类都会继承
  • 父类的方法变量只有非私有时子类可以继承,而private子类不可以继承。

内存图

构造方法是否可以被继承

构造方法是不能被继承的,无论是私有还是非私有。

为什么,构造方法必须要如类名同名。继承这件事本身就是两个不同类名的类完成的,那他还会继承吗

package myextends.a02exendsdemo02;public class Test {public static void main(String[] args) {Zi z1 = new Zi();Zi z2 = new Zi("张三", 210);}
}class Fu {String name;int age;public Fu(){}public Fu(String name, int age) {this.name = name;this.age = age;}
}class Zi extends Fu{}

这段代码会报错。报错内容如下

E:\yang\专业\Java Code\basic-code\day13\src\myextends\a02exendsdemo02\Test.java:6:17
java: 无法将类 myextends.a02exendsdemo02.Zi中的构造器 Zi应用到给定类型;需要: 没有参数找到:    java.lang.String,int原因: 实际参数列表和形式参数列表长度不同

可以这么理解,

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

这段代码肯定不对呀。构造方法必须与类名一致。

Zi z1 = new Zi();而这段代码并不是继承了父类的空参构造方法,而是Java虚拟机自动给子类Zi创建的

如果一个类中没有构造方法,虚拟机会自动的给你添加一个默认的空参构造方法。

成员变量是否可以被继承
非私有的成员变量

分析如下代码的内存图

package myextends.a02exendsdemo02;public class Test {public static void main(String[] args) {Zi z = new Zi();System.out.println(z);z.name = "钢门吹雪";z.age = 23;z.game = "王者荣耀";System.out.println(z.name + ", " + z.age + ", " + z.game);}
}class Fu {String name;int age;
}class Zi extends Fu{String game;
}

这段代码的逻辑是很简单的,现在分析他的内存图。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这段代码的内存图如上图。

这里有两处不一样,第一处是方法区用来存放类的字节码文件。只要方法加载进来了,那么字节码文件就会被加载。因为是zi类继承Fu类,所以Fu类的文件要被加载进来。父类继承Object类,目前Object类对我们没有什么意义。因此不用深度了解。但是要知道Object类也会被加载。

第二处是堆内存,以前new出来的对象和这次nuw出来的对象。还是因为zi类继承Fu类,所以在创建对象的时候,会开辟两部分空间,一部分是继承父类的成员变量,另一部分是自己的成员变量。

被private修饰的成员变量

父类的成员变量被private修饰,子类可以继承,但是不能用

package myextends.a02exendsdemo02;public class Test {public static void main(String[] args) {Zi z = new Zi();System.out.println(z);z.name = "钢门吹雪";z.age = 23;z.game = "王者荣耀";System.out.println(z.game);}
}class Fu {private String name;private int age;
}class Zi extends Fu{String game;
}

代码在运行到z.name = "钢门吹雪";会报错。

E:\yang\专业\Java Code\basic-code\day13\src\myextends\a02exendsdemo02\Test.java:7:10
java: name 在 myextends.a02exendsdemo02.Fu 中是 private 访问控制

通过内存图分析

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

理论上,程序在z.name = "钢门吹雪";处就会停止。最终我还是画完了。

从堆内存的对象这来看,父类的成员变量被子类继承了,如果没有继承,根本不会给父类开辟空间。

当代码执行到z.name = "钢门吹雪";时,因为父类的name由private修饰,因此就不能赋值。可以看到父类中的成员变量是初始值。而子类的成员变量game的值是王者荣耀。

可以是父类中的成员变量nameage被子类继承了,但是子类不能用。

成员方法是否可以被继承

老师说了个例子。我因为markdown图片需要图床,我懒得搭建了,所以我尽量少画图。

public class Test {public static void main(String[] args) {G g = new G();g.methodA();}
}class A {public void methodA(){ }
}class B extends A{public void methodB(){ }
}class C extends B{public void methodC(){ }
}class D extends C{public void methodD(){ }
}class E extends D{public void methodE(){ }
}class F extends E{public void methodF(){ }
}class G extends F{public void methodG(){ }
}

这段代码很变态,看着像是套娃。

最后我在Test类中创建了一个对象g,然后我去调用methodA(),这段代码是可以运行成功的,但是这里需要了解他是怎么运行的。

对象gG类创建的,而G类是由E类继承下来的,而方法methodA()A类的。G类离A类中间有6个间接子类。老师一开始这么说的时候,我以为虚拟机会一层一层向上找,最后找到我要执行的方法methodA(),6个间接子类对代码的运行效率不显著,很快的。如果是600个呢,甚至更多呢

就像你在1楼坐电梯去100楼。你觉得需要花多少时间。

这里就提到了一个**创建对象调用方法的规则:**Java优化了这个需要,Java在顶级类(代码中的A类)设置一个虚方法表。

  • 虚方法:不被privatestaticfinal关键字修饰的方法;
  • 虚方法表:会把当前这个类中的虚方法单独抽取出来,放到虚方法表中。

在继承的应用,父类会把自己的虚方法表交给自己的子类。然后呢子类会在父类的虚方法表基础上添加自己的虚方法。然后又把虚方法表给子类的子类。

对应我上面的代码先是A类把自己的虚方法添加到虚方法表中,然后又把这个表交给B类,然后B类会在A类的基础上把自己的虚方法添加到虚方法表中,又交给C类,C类会在B类和A类的基础上添加后,交给D类以此类推。

因此在执行g.methodA()时,他不会一层一层的网上找,而是直接找G类的虚方法表。找到需要调用的方法。如果需要调用的方法不是一个虚方法,那么他还是要一层一层网上找。

虚方法表有两个作用。

  • 提升代码运行效率,
  • 方法重写

提升代码效率是可以直观感受到的。而方法重写,后面会学。

只有父类中的虚方法才能被子类继承

package myextends.a02exendsdemo02;public class Test {public static void main(String[] args) {Zi z = new Zi();System.out.println(z);z.ziShow();        z.fuShow1();        z.fuShow2();}
}class Fu {public void fuShow1(){System.out.println("public ... fuShow1");}private void fuShow2(){System.out.println("private ... fuShow1");}}class Zi extends Fu{public void ziShow(){System.out.println("public ... ziShow");}}

当然这段代码是错的,因为父类中fuShow2()是被private修饰的不是虚方法,也不能继承。所以z.fuShow2();语句有问题。

还是看一下他的内存图。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这一次把父类的父类Object.class加载进来了。

当全部的类加载进来后,会出现一个虚方法表。在Object.class中会有5个方法加入到虚方法表。然后Fu.class会有一个虚方法加入虚方法表。因为方法fuShow2()被private修饰,所以不会加入。然后是Zi.class会有一个虚方法加入。

在执行zi.ziShow()zi.fuShow1()方法时,因为这两个方法都存在于虚方法表中,因此很快就能找到。而方法fuShow2()不在虚方法表中,会先在Zi.class里面找,发现没有后,继续在他的父类中找,找到了。发现是被private修饰,不能用。

小总结

到这里,什么能继承,什么不能继承,通过内存图的方式就知道了

  • 无论是私有的还是非私有的构造方法都不能继承。因为构造方法与类名一致。而子类和父类的类名必然不一致。
  • 无论是私有还是非私有的成员变量,子类都可以继承,但是被private修饰的成员变量子类不能直接用。
  • 如果成员方法可以被添加到虚方法表中,那么子类是可以继承的,反之,不能继承。

内存分析工具

接着,通过内存分析工具验证一下上面的结论。

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

有这么一段代码,

public class Fu {String name = "Fu";
}public class Zi extends Fu {String name = "Zi";public void ziShow() {String name = "ziShow";System.out.println(name);}
}

问题是在方法ziShow中,输出的name是什么。

先说结论,最近原则

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

就近原则这个词语在前面学习过。根据上面的代码所示,System.out.println(name);输出的是ziShow

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

如果要打印本类的成员变量name和父类的成员变量name呢,

  • 打印本类的成员变量name可以是this.name
  • 打印父类的成员变量name可以是super.name

对的。现在又学到一个关键字super,如果要访问父类的父类,是不行的。

package myextends.a03exendsdemo03;public class Test {public static void main(String[] args) {Zi z = new Zi();z.ziShow();}
}class Fu {String name = "Fu";
}class Zi extends Fu {String name = "Zi";public void ziShow() {String name = "ziShow";System.out.println(name);  // ziShowSystem.out.println(this.name);  // ZiSystem.out.println(super.name);  // Fu}
}

再说另一种情况

package myextends.a03exendsdemo03;public class Test {public static void main(String[] args) {C c = new C();c.cShow();}
}class A {String name = "A";
}class B extends A {String name = "B";
}class C extends B {String name = "B";public void cShow() {String name = "CShow";System.out.println(name);  System.out.println(this.name); System.out.println(super.name); System.out.println(super.super.name); }
}

这段代码是错误的,子类最多能访问到父类,就像上面的代码,获取不到A类的name变量,也没有super.super.name的用法。

继承中:成员方法的访问特点

依旧是就近原则,看如下代码

class Person {public void eat() {System.out.println("吃米饭,吃菜");}public void drink() {System.out.println("喝水");}
}class Student extends Person {public void lunch(){// 先在本类中查看eat()和drink()方法,就会调用子类的,如果没有,就会调用从父类中继承下来的eat()和drink()方法eat();dink();this.eat();this.dink();// 直接调用父类中的eat()和drink()方法super.eat();super.drink();}
}

就这段代码而言,执行eat()this.eat()是没有区别的。

子类中没有eat()drink()方法,我以为执行this.eat()会报错,但是不会报错,而且都指向父类。

class Person {public void eat() {System.out.println("吃米饭,吃菜");}public void drink() {System.out.println("喝水");}
}class OverseasStudent extends Person {public void lunch(){this.eat();  // 吃意大利面this.drink();  // 喝咖啡super.eat();  // 吃米饭,吃菜super.drink();  // 喝水}public void eat() {System.out.println("吃意大利面");}public void drink() {System.out.println("喝咖啡");}
}class Student extends Person {public void lunch(){this.eat(); // 吃米饭,吃菜this.drink();  // 喝水super.eat();  // 吃米饭,吃菜super.drink();  // 喝水}
}

这里又和上面的不一样了。

这里也就t提现到了就近原则。

OverseasStudent类中的lunch()方法中,this.eat()super.eat()就不一样了。触发了就近原则。因为在OverseasStudent类中,我又重新写了eat()和drink()方法。

方法的重写

刚刚,我又重新写了eat()和drink()方法。这就是方法的重写,父类的方法不能满足子类的需求了

  • 定义:当父类的方法不能满足子类现在的需求时,需要进行方法重写
  • 格式:在继承体系中,子类出现了和父类中一模一样的方法声明,我们就城子类这个方法是重写的方法
  • @Override重写注解
    • @Override是放在重写后的方法上,校验子类重写时语法是否正确
    • 加上注解后如果有红色波浪线,表示语法错误
    • 建议重写方法都加上@Override注解,代码更安全,优雅。

注解和注释的区别就是,注释是给我们看的,而注解不仅是给我们看,Java虚拟机还会看的。

package myextends.a04exendsdemo04;public class Test {public static void main(String[] args) {OverseasStudent stu = new OverseasStudent();stu.lunch();}
}class Person {public void eat() {System.out.println("吃米饭,吃菜");}public void drink() {System.out.println("喝水");}
}class OverseasStudent extends Person {public void lunch(){this.eat();  // 吃意大利面this.drink();  // 喝咖啡super.eat();  // 吃米饭,吃菜super.drink();  // 喝水}@Overridepublic void eat() {System.out.println("吃意大利面");}@Overridepublic void drink() {System.out.println("喝咖啡");}
}
重写的本质

覆盖虚方法表,如果发生了重写,那么就会覆盖虚方法表中的虚方法。

重写发送在子父类,继承时,假设有一种继承结构

A
+ method2 null
B
+ method2 null
C
+ method1 null
+ method2 null

怎么解释呢,

每个类都有自己的虚方法表。

在这段继承结构中,最大的类是C类,那么C类的虚方法表就是C: method1()C: method2()

接着B类继承C类,然后B类重写了method2(),可以得出B类的虚方法表就是C: method1()B: method2()

A类继承B类,A类重写了method2(),可以得出A类的虚方法表就是C: method1()A: method1()

在这个时候的三种情况,我用代码演示说明

public class Test {public static void main(String[] args) {A a = new A();a.method1();a.method2();}
}

a.method1();时,method1();调用的是A类的虚方法表,正是C: method1()

a.method2();时,method2();调用的是A类的虚方法表,正是A: method2()

public class Test {public static void main(String[] args) {B b = new b();a.method2();}
}

b.method2();时,method2();调用的是B类的虚方法表,正是B: method2()

public class Test {public static void main(String[] args) {C a = new C();c.method2();}
}

c.method1();时,method2();调用的是C类的虚方法表,正是C: method2()

重写的注意事项
  1. 重写方法的名称、形参列表必须与父类中的一致。
  2. 子类重写父类方法时,访问权限子类必须大于等于父类(暂时了解:空着不写 < protected < public)
  3. 子类重写父类方法时,返回值类型子类必须小于等于父类
  4. 建议:重写的方法尽量和父类保持一致。
  5. 私有方法不能被重写。
  6. 子类不能重写父类的静态方法,如果重写会报错的。

直接复制老师的,我应该不会犯错误,私有方法,你不能继承,那你就根本不能重写

直接是

只有被添加到虚方法表中的方法才能被重写

class Person {public void eat() {System.out.println("吃米饭,吃菜");}
}class OverseasStudent extends Person {@Overridepublic void eat(int a) {System.out.println("吃意大利面");}
}

方法的重写,方法名必须是一致的,这个是不会出现错误的,但是我上面代码是有问题的,因为父类的eat()方法没有形参,而子类的eat(int a)方法含有形参。要不都有,要不都没有并且形参一致。

class Person {public void eat() {System.out.println("吃米饭,吃菜");}
}class OverseasStudent extends Person {@Overrideprotected void eat() {System.out.println("吃意大利面");}
}

这段代码是有问题的

class Person {protected void eat() {System.out.println("吃米饭,吃菜");}
}class OverseasStudent extends Person {@Overridepublic void eat() {System.out.println("吃意大利面");}
}

虽然权限还是不一样,但是这段代码是对的。

子类重写父类方法时,访问权限子类必须大于等于父类(暂时了解:空着不写 < protected < public)

public权限大,protected权限小。子类方法权限可以和父类不一样,但是必须要大于父类。

如果有以下继承关系

class Animal {}
class Dog extends Animal{}
class Cat extends Animal{}

从字面意思来说,动物类的范围肯定大于猫和狗。Dog类和Cat类同级都小于Animal类

父类的范围都是要比子类小的。父与子。子小

class Person {public Animal eat() {System.out.println("吃米饭,吃菜");return unll;}
}class OverseasStudent extends Person {@Overridepublic Dog eat() {System.out.println("吃意大利面");return unll;}
}

虽然两个方法的返回值不一样,但是返回类型是Dog的小于Animal,所以代码正确。

如果反过来。

class Person {public Dog eat() {System.out.println("吃米饭,吃菜");return unll;}
}class OverseasStudent extends Person {@Override+public Animal eat() {System.out.println("吃意大利面");return unll;}
}

代码错误,父类的方法返回值类型必须要比子类大。

class Person {private void eat() {System.out.println("吃米饭,吃菜");}
}class OverseasStudent extends Person {@Overridepublic void eat() {System.out.println("吃意大利面");}
}

父类的eat()方法被private修饰,私有的,都没有被加入虚方法表。那就更不可能被重写了。代码错误。

利用方法的重写设计继承结构

题目:

现在有三种动物:哈士奇、沙皮狗、中华田园犬。暂时不考虑属性,只要考虑行为。请按照继承的思想特点进行继承体系的设计。

三种动物分别有以下的行为:
哈士奇:吃饭(吃狗粮)、喝水、看家、拆家
沙皮狗:吃饭(吃狗粮、吃骨头)、喝水、看家
中华田园犬:吃饭(吃剩饭)、喝水、看家

先画类图,在进行程序编写

Husky
breakHome()
SharPei
eat() : null
ChineseDog
eat() : null
Dog
eat() : null
drink() : null
lookHome() : null

接着是代码设计

Dog.java

package myextends.a06exendsdemo06;public class Dog {public void eat() {System.out.println("狗在吃狗粮");}public void drink() {System.out.println("狗在喝水");}public void lookHome() {System.out.println("狗在看家");}
}

Husky,java

package myextends.a06exendsdemo06;public class Husky extends Dog{public void breakHome(){System.out.println("哈士奇在拆家");}
}

SharPei.java

package myextends.a06exendsdemo06;public class SharPei extends Dog{@Overridepublic void eat() {super.eat();  // 吃狗粮System.out.println("狗在啃骨头");}
}

SharPei类把父类Dog类中的eat方法重写了、因为沙皮狗也会吃狗粮。会使用父类eat方法里面的代码。可以用笨方法复制代码,这里推荐super.eat()

ChineseDog.java

package myextends.a06exendsdemo06;public class ChineseDog extends Dog{@Overridepublic void eat() {System.out.println("狗在吃剩饭");}
}

DogTest.java

package myextends.a06exendsdemo06;public class DogTest {public static void main(String[] args) {Husky h = new Husky();h.eat();h.drink();h.lookHome();h.lookHome();SharPei sp = new SharPei();sp.eat();sp.drink();sp.lookHome();ChineseDog cd = new ChineseDog();cd.eat();cd.drink();cd.lookHome();}
}

运行结果:

D:\Develop\Java\jdk-17\bin\java.exe "-javaagent:D:\Develop\JetBrains\IntelliJ IDEA Community Edition 2024.1.2\lib\idea_rt.jar=51190:D:\Develop\JetBrains\IntelliJ IDEA Community Edition 2024.1.2\bin" -Dfile.encoding=UTF-8 -classpath "E:\yang\专业\Java Code\basic-code\out\production\day13" myextends.a06exendsdemo06.DogTest
狗在吃狗粮
狗在喝水
狗在看家
狗在看家
狗在吃狗粮
狗在啃骨头
狗在喝水
狗在看家
狗在吃剩饭
狗在喝水
狗在看家Process finished with exit code 0

继承中:构造方法的特点

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

两点中,第一点已经被验证过。

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

比如说,执行代码Zi z = new Zi();后,代码会先执行public Fu(){}然后是public Zi(){}

为什么会这么设计

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

其实是可以想明白的,继承嘛,你会把父类的一部分东西继承给子类,例如姓名、性别、年龄等。在赋值时,应该给父类先赋值这些内容。再说了,子类里面没有这些东西。通过子类访问这些数据时,z.namename会在父类。不先给父类赋值,是找不到的。

  • 子类构造方法的第一行语句默认都是:super(),不写也存在,且必须在第一行
  • 如果想调用父类的无参构造,必须手动写super进行调用
package myextends.a05exendsdemo05;public class Test {public static void main(String[] args) {Zi z = new Zi();}
}class Fu {String name;int age;public Fu() {System.out.println("父类的无参构造方法");}public Fu(int age, String name) {this.age = age;this.name = name;}
}class Zi extends Fu{}

大胆猜测一下,这段程序运行的结构是什么。

父类的无参构造方法

虽然我在Zi类里面什么都没有写,但是最后是有输出结果的。

当Zi类里面没有写任何构造方法时,会默认创建一个空参构造方法。而在Java虚拟机自动创建的空参构造里面的第一行语句是super();

类似于下面的代码

class Zi extends Fu{public Zi() {super();}
}

接下来验证super语句必须是第一行。

    public Zi() {System.out.println("子类的无参构造");super();}

代码会报错,因为super();必须在第一行。

package myextends.a05exendsdemo05;public class Test {public static void main(String[] args) {Zi z = new Zi();}
}class Fu {String name;int age;public Fu() {System.out.println("父类的无参构造方法");}public Fu(String name, int age) {this.age = age;this.name = name;}
}class Zi extends Fu{public Zi() {super();System.out.println("子类的无参构造方法");}
}

这段代码的执行逻辑:

父类的无参构造方法
子类的无参构造方法

如果是用带参构造去创建对象呢,比如Zi z = new Zi("zhangsan", 23)。那么首先就要在Zi类下面创建带参的构造方法,然后还是是有super(name, age)

public Zi(String name, int age) {super(name, age)
}

完整代码:

package myextends.a05exendsdemo05;public class Test {public static void main(String[] args) {Zi z = new Zi("zhangsan", 23);System.out.println(z.name + ", " + z.age);}
}class Fu {String name;int age;public Fu() {System.out.println("父类的无参构造方法");}public Fu(String name, int age) {this.age = age;this.name = name;System.out.println("父类的带参构造方法");}
}class Zi extends Fu{public Zi() {super();System.out.println("子类的无参构造方法");}public Zi(String name, int age) {super(name, age);System.out.println("子类的带参构造方法");}
}

执行结果是:

父类的带参构造方法
子类的带参构造方法
zhangsan, 23

代码的运行逻辑也就显而易见了。

当然也可以访问

this、super使用总结

  • this:理解为一个变量,表示当前方法调用者的地址值
  • super:代码父类存储空间。

this在前面的代码中是写过的,谁要去调用这个方法,那么谁的地址值就给了this。

而super用于访问父类的内容。

关键字访问成员变量访问成员方法访问构造方法
thisthis.成员变量
访问本类成员变量
this.成员方法(...)
访问本类成员方法
this(...)
访问本类构造方法
supersuper.成员变量
访问父类成员变量
super.成员方法
访问父类成员方法
`super(…)
访问父类构造方法

在这里,this()咱们还没有了解。通过代码也是可以快速了解的。

package myextends.a06exendsdemo06;public class Test {public static void main(String[] args) {Student stu = new Student();stu.name = "zhangsan";stu.age = 23;System.out.println(stu.name + ", " + stu.age + ", " + stu.school);}
}class Student{String name;int age;String school;public Student() {// 调用本类其他构造方法// 细节:虚拟机不会在添加superthis(null, 0, "传智大学");}public Student(String name, int age, String school) {this.name = name;this.age = age;this.school = school;}
}

this(null, 0, "传智大学");就相当于调用了全餐构造方法public Student(String name, int age, String school){}

因此this上面不能在用其他语句,this必须处于首句

public Student() {System.out.println("111");this(null, 0, "传智大学");
}

代码汇报错。原因是this(null, 0, "传智大学");不处于句首。

这个的用于指定默认值,现在school的默认值就是"传智大学"。当然必需要空参构造方法、你用全参构造方法,school空的不写会报错

练习:带有继承结构的标准Javabean类

复习,什么是标准Javabean类

  • 类名见名知意;
  • 所有的成员变量都需要私有;
  • 构造方法(空参带全部参数的构造)
  • get/set
练习1

题目:

1.经理
成员变量:工号,姓名,工资,管理奖金
成员方法:工作(管理其他人),吃饭(吃米饭)
2.厨师
成员变量:工号,姓名,工资
成员方法:工作(炒菜),吃饭(吃米饭)

先绘制图

Manager
Cook
Employee

Employee.java

package myextends.a07exendsdemo07;public class Employee {private String id;private String name;private double salary;public Employee() {}public Employee(String id, String name, double salary) {this.id = id;this.name = name;this.salary = salary;}public String getId() {return id;}public void setId(String id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public double getSalary() {return salary;}public void setSalary(int salary) {this.salary = salary;}public void work(){System.out.println("员工在工作");}public void eat(){System.out.println("员工在吃米饭");}
}

Manager.java

package myextends.a07exendsdemo07;public class Manager extends Employee{private double bonus;public Manager() {}public Manager(String id, String name, double salary, double bonus) {super(id, name, salary);this.bonus = bonus;}public double getBonus() {return bonus;}public void setBonus(double bonus) {this.bonus = bonus;}@Overridepublic void work() {System.out.println("员工在管理员工");}
}

Cook.java

package myextends.a07exendsdemo07;public class Cook extends Employee{public Cook() {}public Cook(String id, String name, double salary) {super(id, name, salary);}@Overridepublic void work() {System.out.println("厨师正在炒菜");}
}

EmployeeTest.java

package myextends.a07exendsdemo07;public class EmployeeTest {public static void main(String[] args) {Manager m = new Manager("001", "张三", 10000, 5000);System.out.println(m.getId() + ", " +  m.getName() + ", "+ m.getSalary() + ", " + m.getBonus());Cook c = new Cook("002", "李四", 8000);System.out.println(c.getId() + ", " + c.getName() + ", " + c.getSalary());}
}

在这里有个细节,虽然经理和厨师的work都被重写了,你为什么要还有抽取呢。

每个员工都要工作。如果还有其他员工呢,没有指定工作内容。那就调用默认的工作方法。

练习2

题目:

在黑马程序员中有很多员工(Employee)。按照工作内容不同分教研部员工(Teacher)和行政部员工(AdminStaff)

  1. 教研部根据教学的方式不同又分为讲师(Lecturer)和助教(Tutor)
  2. 行政部根据负责事项不同,又分为维护专员( Maintainer),采购专员(Buyer))
  3. 公司的每一个员工都编号,姓名和其负责的工作
  4. 每个员工都有工作的功能,但是具体的工作内容又不一样。
Lecturer
+work()
Tutor
+work()
Maintainer
+work()
Buyer
+work()
Teacher
+work()
AdminStaff
+work()
Employee
- String number
- String name
- String career
+work()

多态

多态的介绍

老师是这么引入的,学校的教务管理系统。大家都用过。学生可以登录,老师可以登录、管理员也可以登录。现在要写这个系统的注册业务逻辑。可以想到的是吧注册写成一个方法register()。那么这个方法的形参又该怎么写。

老师让我们暂停视频自己思考。我是这么想的,形参无非就是用户名、密码、确认密码这些吗

public void register(String username, String password, String againPassword){// 注册逻辑
}

这不是很简单嘛。但是老师是这么思考的,把整个学生对象放了进去。

public void register(Student s){}

老师这么想是正确的,把前面的知识应该用起来。把用户名、密码等等这些数据封装起来。

问题来了,现在这个注册的方法只能是注册学生的,那老师和管理员怎么办。好办,方法可以重载

public void register(Teacher t){}
public void register(Admin a){}

这样是可以行得通的,如果还有其他角色,还要继续写下去吗。这样的话会很麻烦。

这里就用到了多态。多种形态、

public void register(Person p){}

这个的前提是学生、老师、管理员都继承人。

让这个注册方法的形参写成他们的父类person。这样不同的角色都可以注册了。前提的不同的角色都基础油person。

因此什么是多态

同类的对象,表现出不同的形态

多态的表现形式

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

多态的前提

  • 有继承或实现关系
  • 有父类引入指向子类对象
  • 有方法重写

第二条指向的意思是Fu f = new Zi(),父类Fu通过等于执行子类对象Zi,实现是接口的内容,快要学了。

public class Test {public static void main(String[] args) {Student s = new Student();s.setName("张三");s.setAge(18);Teacher t = new Teacher();t.setName("王建国");t.setAge(30);Administrator admin = new Administrator();admin.setName("admin");admin.setAge(35);register(s);register(t);register(admin);}public static void register(Person p){p.show();}
}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);}
}class Student extends Person{@Overridepublic void show() {System.out.println("学生的信息是: " + super.getName() + ", " + super.getAge());}
}class Teacher extends Person{@Overridepublic void show() {System.out.println("老师的信息是: " + super.getName() + ", " + super.getAge());}
}class Administrator extends Person{@Overridepublic void show() {System.out.println("管理员的信息是: " + super.getName() + ", " + super.getAge());}
}

代码的运行结果是

学生的信息是: 张三, 18
老师的信息是: 王建国, 30
管理员的信息是: admin, 35

上面的代码就是应该多态形式。

在每个子类里面都重写了show方法。

可以理解成让注册方法是多种形态,不再是只能接收同一种形态的数据,

多态调用成员变量的特点

package polymorohism.a02polymorohismdemo02;public class Test {public static void main(String[] args) {// 用多态方式创建对象 Fu f = new Z()lAnimal a = new Dog();// 调用成员变量:编译看左边,运行也看左边// 编译看左边:Javac编译代码的时候,会看左边的父类中有没有这个变量,如果有,编译成功,如果没有,编译失败。// 运行也看左边:Java运行代码的时候,实际获取的就是左边父类中成员变量的值。System.out.println(a.name);  // 动物// 调用成员方法,编译看左边,运行看右边// 编译看左边:Javac编译代码的时候,会看左边的父类中有没有这个方法,如果有,编译成功,如果没有,编译失败。// 运行也看左边:Java运行代码的时候,实际获取的是子类中的方法。a.show();  // Dog...show// 理解:// Animal a = new Dog();// 现在用a去调用变量和方法的呀?是的// 而a是Animal类型的。所以默认都会从Animal这个类中去找// 成员变量:在子类的对象中,会把父类的成员变量也继承下的,父:name,子:name// 成员方法:如果子类对方法进行了重写,那么在虚方法表中是会把父类的方法进行覆盖的。//}
}class Animal{String name = "动物";public void show(){System.out.println("Animal...show");}
}class Dog extends Animal{String name = "狗";@Overridepublic void show(){System.out.println("Dog...show");}
}class Cat extends Animal{String name = "猫";@Overridepublic void show(){System.out.println("Cat...show");}
}

这里搞得云里雾里。得出了这么一个结论是如果有的多态形式创建对象 Animal a = new Dog();那么。a.name中name是父类成员变量的数据,而a.show()调用的是子类的成员方法。

从内存图角度分析

package polymorohism.a03polymorohismdemo03;public class Test {public static void main(String[] args) {Animal a = new Dog();System.out.println(a.name);  // 动物a.show();  // Dog...show}
}class Animal{String name = "动物";public void show(){System.out.println("Animal...show");}
}class Dog extends Animal {String name = "狗";@Overridepublic void show(){System.out.println("Dog...show");}
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这个图,其实我感觉我没看出来。但是。大致就是这个意思。我理解到位了。

总之

  • 调用成员变量的特点:编译看左边,运行也看左边
  • 调用成员方法的特点:编译看左边,运行看右边

多态的优势和弊端

优势

在多态形势下,右边对象可以实现解耦合,便于扩展维护

这句话我没有理解,但是老师说的例子我明白了。

Person p = new Student();
p.work();

p.work实际上调用的是Student里的work方法。学生工作

如果过几天,我想改成老师工作,那么就只需要吧new Student()改成new Teacher(),这么一来,是不是简单了。

定义方法的时候,使用父类型作为参数,可以接收所有子类对象,体现多态的扩展与便利

这条在上面体现过。

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

在学习字符串的时候,学习过StringBuilder对象

StringBuilder sb = new StringBuilder();

​ 当在idea中sb.append();的时候,会有一个append(Object obj)的提示,这就是多态的体系。

所有类都会继承Object,那么这个函数就可以接收不同的对象

StringBuilder sb = new StringBuilder();
sb.append(a);
System.out.println(sb);  // polymorohism.a03polymorohismdemo03.Dog@41629346

程序运行是正确的,但是这个结果我是看不懂。

还有集合,我们之前是这么使用的

ArrayList <String> list = new ArrayList<>();

其实可以不用指定泛型,

ArrayList list = new ArrayList();

那么在idea编码的时候list.add()这个里面的形参就是Object。

ArrayList list = new ArrayList();
list.add(a);
System.out.println(list);  // [polymorohism.a03polymorohismdemo03.Dog@41629346]

代码运行成功

弊端

package polymorohism.a04polymorohismdemo04;public class Test {public static void main(String[] args) {// 多态方式创建对象Animal a = new Dog();// 编译看左边,运行看右边a.eat();  // 狗吃骨头// 多态的弊端// 不能调用子类特有功能// 原因,当调用成员方法时, 编译看左边,运行看右边// 那么在编译的时候会先检查左边的父类中有没有这个方法。没有直接报错
//        a.lookHome();// 解决方法// 变回子类类型即可
//        Dog d = (Dog) a;
//        d.lookHome();  // 狗在看家// 细节,转换的时候不能瞎转,如果转换成其他类的类型会报错
//        Cat c = (Cat) a;  // class polymorohism.a04polymorohismdemo04.Dog cannot be cast to class polymorohism.a04polymorohismdemo04.Cat// 判断a是不是Dog类型
//        if (a instanceof Dog){
//            Dog d = (Dog) a;
//            d.lookHome();
//        } else if (a instanceof Cat) {
//            Cat c = (Cat) a;
//            c.catchMouse();
//        } else {
//            System.out.println("没有这个类型");
//        }// 新特性// 先判断a是不是Dog类,如果是,强转成Dog类型,转换之后变量名为d// 如果不是不强转,结果是falseif (a instanceof Dog d){d.lookHome();} else if (a instanceof Cat c) {c.catchMouse();} else {System.out.println("没有这个类型");}}
}class Animal{public void eat(){System.out.println("动物在吃东西");}
}class Dog extends Animal{@Overridepublic void eat() {System.out.println("狗吃骨头");}public void lookHome(){System.out.println("狗在看家");}
}class Cat extends Animal{@Overridepublic void eat() {System.out.println("猫吃小鱼干");}public void catchMouse(){System.out.println("猫在抓老鼠");}
}

多态的弊端是不能调用子类特有的方法。因为父类没有子类特有的方法

当调用成员方法时, 编译看左边,运行看右边

这句话是要牢记的。如果我非用不可怎么办。强转,

Dog d = (Dog) a;

但是强转并不是什么都能转,要遵循规则。创建的是狗,如果强转成猫,会报错

Exception in thread "main" java.lang.ClassCastException: class polymorohism.a04polymorohismdemo04.Dog cannot be cast to class polymorohism.a04polymorohismdemo04.Cat (polymorohism.a04polymorohismdemo04.Dog and polymorohism.a04polymorohismdemo04.Cat are in unnamed module of loader 'app')at polymorohism.a04polymorohismdemo04.Test.main(Test.java:22)

上面的内容是报错日志。

这种问题可以避免。我也不知道a是什么类型。

if (a instanceof Dog d){d.lookHome();
} else if (a instanceof Cat c) {c.catchMouse();
} else {System.out.println("没有这个类型");
}

这个是jdk14的新特性。先判断a是不是Dog类,如果是,强转成Dog类型,转换之后变量名为d

包的介绍

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

在python中也存在这个概念。这是一种代码的组织方式。比如说做个应用。你不可能吧全部代码放在一个文件里吧,也不可能把几个文件把中项目的根目录把、这时就需要包。

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

我没有公司域名,咱们中做代码练习的时候,基本上就是domain清楚这个包做什么就行了。

package com.itheima,domein;public class Student {}

在我之前写的代码的时候,一直搞不懂package com.itheima,domein;是什么意思,idea自动添加。现在就是可以解释了。意思是Student类存在于com.itheima,domein这个包

然后之前使用这个类的时候,咱们的代码是

Student stu = new Student();

现在多了一种方式

com.itheima,domein.Student stu = new com.itheima,domein.Student();

这种方式调用有个新名词,全类名或者全限定名。

使用其他类的规则

public class Test {public static void main(String[] args) {com.itheima,domein.Student stu = new com.itheima,domein.Student();        }
}

在使用其他类时需要用全类名。但是这么写属实麻烦。因此出了一个新名词,导包、

import com.itheima,domein.Student;public class Test {public static void main(String[] args) {Student stu = new Student();        }
}

import com.itheima,domein.Student;这条代码也很常见,idea会自动添加。

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

final

final是最终的意思,被修饰的内容不可以改变。可以修饰方法、类、变量。

final修饰方法

如果被final修饰的方法,这个方法是最终方法;就不可以重写了。

package myfinal.a01finaldemo01;public class Test {public static void main(String[] args) {Zi zi = new Zi();}
}class Fu{public final void show(){System.out.println("父类的show()方法");}
}class Zi extends Fu{@Overridepublic void show(){System.out.println("子类的show()方法");}
}

这段代码是有问题的,

E:\yang\专业\Java Code\basic-code\day13\src\myfinal\a01finaldemo01\Test.java:17:17
java: myfinal.a01finaldemo01.Zi中的show()无法覆盖myfinal.a01finaldemo01.Fu中的show()被覆盖的方法为final

看他的报错内容,意思就是被final修饰的方法不能被重写

这个用于一些不写被别人改变的方法。用final

final修饰类

如果被final修饰的类,最终类,这个类不可以继承了。

package myfinal.a01finaldemo01;public class Test {public static void main(String[] args) {Zi zi = new Zi();}
}final class Fu{public void show(){System.out.println("父类的show()方法");}
}class Zi extends Fu{@Overridepublic void show(){System.out.println("子类的show()方法");}
}

这段代码与上面的代码是相同的,但是现在修饰的Fu类。代码还是会报错

E:\yang\专业\Java Code\basic-code\day13\src\myfinal\a01finaldemo01\Test.java:15:18
java: 无法从最终myfinal.a01finaldemo01.Fu进行继承

最终类,不能被继承

final修饰变量

如果被final修饰的变量,常量,只能赋值一次

public class Test {public static void main(String[] args) {final int a = 10;a = a+1;System.out.println(a );}
}

这段代码会报错

E:\yang\专业\Java Code\basic-code\day13\src\myfinal\a01finaldemo01\Test.java:6:9
java: 无法为最终变量a分配值

但是这个a是可以打印的。也可以使用

public class Test {public static void main(String[] args) {final int a = 10;
//        a = a+1;System.out.println(a);  // 10System.out.println(a + 1);  // 11}
}

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

常量的命名规范:

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

细节:

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

final修饰基本数据类型的细节已经体现过了。被final修饰的变量不能发生改变。

而final修饰引用数据类型,地址值不该,但是对象的内部(属性)可以改变

package myfinal.a02finaldemo02;public class Test {public static void main(String[] args) {final Student stu = new Student("张三", 20);System.out.println(stu.getName() + ", " + stu.getAge());  // 张三, 20stu.setName("李四");stu.setAge(25);System.out.println(stu.getName() + ", " + stu.getAge());  // 李四, 25//        stu = new Student();  // java: 无法为最终变量stu分配值final int[] ARR = {1,2,3,4,5};ARR[0] = 11;ARR[5] = 55;//        ARR = new int[10];  // java: 无法为最终变量ARR分配值}
}class Student {private String name;private int age;public Student() {}public Student(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;}
}

很好理解。在执行stu.setName("李四");stu.setAge(25); ARR[0] = 11;ARR[5] = 55;的时候,stu还是ARR他们保存的地址值没有发生变化、

但是new的话,就会开辟新的内存,地址值发送改变了,那就不行了。

权限修饰符

  • 权限修饰符:是用来控制有个成员能够被访问的范围的
  • 可以修饰成员变量,方法,构造方法,内部类。

这个名词是刚刚遇到的,但是这个功能咱们是从一开始就用到的

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

这段代码中,public、private就是权限修饰符。

在Java中一共有四种权限修饰符。通过作用范围从小到大的顺序排列是private< 空着不写(缺省/默认) < protected < public。有这么一张表格

修饰符同一个类中同一个包中其他类不同包下的子类不同包下的无关类
private
空着不写
protected
public

这个表格应该怎么理解呢。在上面已经学习过包了、private范围最小,而public范围最大

在开发中遵循一下规则即可

实际开发中,一般只用orivate和oublic

  • 成员变量私有
  • 方法公开

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

代码块

什么是代码块,老师是这么演示的

public class Test {public static void main(String[] args) {{int a = 10;System.out.println(a);  // 10}}
}

代码块根据位置的不同分为

  • 可以写在方法里面的局部代码块
  • 可以写在成员位置也就是方法外类里面的构造代码块
  • 加上static进行修饰的静态代码块

局部代码块

听着很高大上,但是已经淘汰了。

public class Test {public static void main(String[] args) {{int a = 10;System.out.println(a);  // 10}}
}

这就是局部代码块的应用。

为什么说是淘汰了。这对大括号的作用就是提前结束变量的生命周期。咱们之前学习过,变量的作用范围只在所属的范围内有效。

package mycodeblock;public class CodeBlockDemo1 {public static void main(String[] args) {{int a = 10;}System.out.println(a); }
}

这段代码会发生错误,原因是找不到a,虽然是定义a了。定义的 a在大括号里面,而我的sout语句在大括号外面。

E:\yang\专业\Java Code\basic-code\day13\src\mycodeblock\CodeBlockDemo1.java:8:28
java: 找不到符号符号:   变量 a位置: 类 mycodeblock.CodeBlockDemo1

提前结束变量生命周期。防止内存占用。但是现在的内存很大了。根本不用考虑这个问题了。

构造代码块

class Student02{private String name;private int age;public Student02() {System.out.println("开始创建对象了");}public Student02(String name, int age) {System.out.println("开始创建对象了");this.name = name;this.age = age;}
}

这段代码想要做什么呢。就是在创建对象的时候都想要输出一句开始创建对象了。无论是空参构造方法还是有参。

上面的代码是在每一个构造方法里面写入了一句开始创建对象了。如果要插入很多行语句呢,这么写的话,代码可读性不高,因此需要改这些代码

class Student02{private String name;private int age;{System.out.println("开始创建对象了");}public Student02() {}public Student02(String name, int age) {this.name = name;this.age = age;}
}

构造代码块有如下逻辑

构造代码块:

  • 写在成员位置的代码块
  • 作用:可以把多个构造方法中重复的代码抽取出来
  • 执行时机:我们在创建本类对象的时候会先执行构造代码块再执行构造方法

我们应该注意他的有限时机,构造代码块会先执行,然后是构造方法

package mycodeblock;public class CodeBlockDemo2 {public static void main(String[] args) {Student02 stu0201 = new Student02();Student02 stu0202 = new Student02("张三", 22);}
}class Student02{private String name;private int age;{System.out.println("开始创建对象了");}public Student02() {System.out.println("空参构造方法");}public Student02(String name, int age) {System.out.println("有参构造方法");this.name = name;this.age = age;}
}

这段代码的输出结果如下

D:\Develop\Java\jdk-17\bin\java.exe "-javaagent:D:\Develop\JetBrains\IntelliJ IDEA Community Edition 2024.1.2\lib\idea_rt.jar=55835:D:\Develop\JetBrains\IntelliJ IDEA Community Edition 2024.1.2\bin" -Dfile.encoding=UTF-8 -classpath "E:\yang\专业\Java Code\basic-code\out\production\day13" mycodeblock.CodeBlockDemo2
开始创建对象了
空参构造方法
开始创建对象了
有参构造方法Process finished with exit code 0

这个技术也不常用了。如果我还写一个构造方法。但是我不希望执行开始创建对象了。这个时候就要回到原始,

如果我们要写多个构造方法,其中有几个代码块中有重复的语句。怎么办。不难

第一种方法就是咱妈学过的,把重复的代码抽取成方法。

class Student02{private String name;private int age;{}public Student02() {调用方法();}public Student02(String name, int age) {调用方法();this.name = name;this.age = age;}public Student02(String name) {this.name = name;}
}

还有一个用this。在前面学习过, this(...)访问本类构造方法,把相同的代码块给其中应该构造方法。

class Student02{private String name;private int age;public Student02() {this(null, 0);  // 不知道值。默认}public Student02(String name, int age) {System.out.println("开始创建对象了");this.name = name;this.age = age;}public Student02(String name) {this.name = name;}
}

静态代码块

  • 格式:static{}
  • 特点:需要通过static关键字修饰,随着类的加载而加载,并且自动触发、只执行一次
  • 使用场景:在类加载的时候,做一些数据的初始化的时候使用。
public class Test {public static void main(String[] args) {Student s1 = new Student();Student s2 = new Student("张三", 23);}
}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;}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;}
}

这段代码的执行结果是

D:\Develop\Java\jdk-17\bin\java.exe "-javaagent:D:\Develop\JetBrains\IntelliJ IDEA Community Edition 2024.1.2\lib\idea_rt.jar=61260:D:\Develop\JetBrains\IntelliJ IDEA Community Edition 2024.1.2\bin" -Dfile.encoding=UTF-8 -classpath "E:\yang\专业\Java Code\basic-code\out\production\day13" mycodeblock.a01codeblockdemo01.Test
静态代码块执行了。
空参构造方法
含参构造方法Process finished with exit code 0

静态代码块是很有用处的,用于数据初始化。

我想用一个集合去存放学生数据,然后打印。如果按照原有的思想去写这个代码

package mycodeblock.a02codeblockdemo02;import java.util.ArrayList;public class Test {public static void main(String[] args) {ArrayList<Student> list = new ArrayList<>();list.add(new Student("hhh001", "张三", 24));list.add(new Student("hhh002", "李四", 20));list.add(new Student("hhh003", "王五", 22));for (int i = 0; i < list.size(); i++) {Student s = list.get(i);System.out.println(s.getId() + ", " + s.getName() + ", " + s.getAge());}}
}

这么写,程序是可以实现的,但是有个弊端。main方法也是可以被调用的。还可以反复调用。

package mycodeblock.a02codeblockdemo02;public class Test2 {public static void main(String[] args) {Test.main(null);Test.main(null);Test.main(null);Test.main(null);Test.main(null);Test.main(null);}
}

如果是这样的话,程序反复执行,那么集合和Student也会反复创建、

ArrayList<Student> list = new ArrayList<>();
list.add(new Student("hhh001", "张三", 24));
list.add(new Student("hhh002", "李四", 20));
list.add(new Student("hhh003", "王五", 22));

这样是很占用内存的,因此用到了静态代码块。我直接写成完整代码

package mycodeblock.a02codeblockdemo02;import java.util.ArrayList;public class Test {static ArrayList<Student> list = new ArrayList<>();static {list.add(new Student("hhh001", "张三", 24));list.add(new Student("hhh002", "李四", 20));list.add(new Student("hhh003", "王五", 22));}public static void main(String[] args) {for (int i = 0; i < list.size(); i++) {Student s = list.get(i);System.out.println(s.getId() + ", " + s.getName() + ", " + s.getAge());}}
}class Student {private String id;private String name;private int age;public Student() {}public Student(String id, String name, int age) {this.id = id;this.name = name;this.age = age;}public String getId() {return id;}public void setId(String id) {this.id = id;}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 static void main(String[] args) {Scanner sc = new Scanner(System.in);ArrayList<User> userList = new ArrayList<>();User u = new User("admin", "Admin00000", "11122223333", "11122233334455667x");userList.add(u);...
}

但是这种弊端,反复调用,内存占用。现在数据量不算多,如果说是占用,并不会占用多少、

这里就可以用静态代码块解决。在加载类的时候,就初始化一些必要的数据。

public class AppItheima {static ArrayList<User> userList = new ArrayList<>();static {User u = new User("admin", "Admin00000", "11122223333", "11122233334455667x");userList.add(u);}public static void main(String[] args) {...}
}

三种代码块中,局部代码块和构造代码块已经是out了,不是很常用。静态代码块是一个好东西。用于初始化。

抽象

还是用学生和老师举例子。学生和老师都继承于人。学生和老师的工作是不一样的,因此work方法需要被重写。

现在我在学生类里面忘记重写work方法或者直接忽略重写。代码是跑的通的。但是不符合我的设想。

因此在父类中把work定义成抽象方法。凡是被定义成抽象方法的方法都要被重写。否则报错;

在一个类中,无论有一个或者多个抽象方法,那么这个类要被定义成抽象类、

抽象类和抽象方法的定义格式

  • 抽象方法的定义格式:

    public abstract 返回值类型 方法名(列表参数);
    
  • 抽象类的定义格式

    public abstract clss 类名{}
    
package myabstract.a01abstractdemo1;public abstract class Person {public abstract void work();
}

以上就是一个简单的抽象类和抽象方法的定义格式

抽象类和抽象方法的注意事项

  • 抽象类不能实例化

  • 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类

  • 可以有构造方法

  • 抽象类的子类

    要么重写抽象类中的所有抽象方法

    要么是抽象类

这些规则不需要死记硬背,现在的idea功能已经很强大了,你犯错了直接会报错

package myabstract.a01abstractdemo1;public class Test {public static void main(String[] args) {Person p = new Person();}
}

这段代码idea直接在new Person()处标红了。也可以运行,直接报错

E:\yang\专业\Java Code\basic-code\day13\src\myabstract\a01abstractdemo1\Test.java:5:20
java: myabstract.a01abstractdemo1.Person是抽象的; 无法实例化

抽象类不可以实例化,也就是创建对象

package myabstract.a01abstractdemo1;public abstract class Person {
//    public abstract void work();public void eat(){System.out.println("正在睡觉");}
}

这种情况是允许的,是抽象类,但是没有抽象方法。

package myabstract.a01abstractdemo1;public class Person {public abstract void work();
}

idea会在abstract处标红。在测试类中创建对象会报错

E:\yang\专业\Java Code\basic-code\day13\src\myabstract\a01abstractdemo1\Person.java:3:8
java: myabstract.a01abstractdemo1.Person不是抽象的, 并且未覆盖myabstract.a01abstractdemo1.Person中的抽象方法work()

一下演示一个正确的代码

package myabstract.a01abstractdemo1;public class Test {public static void main(String[] args) {Student s = new Student("张三", 23);s.work();}
}abstract 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 abstract void work();
}class Student extends Person{public Student() {}public Student(String name, int age) {super(name, age);}@Overridepublic void work() {System.out.println("学生正在学习");}
}

抽象类不能创建对象,但是有构造方法是为什么

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

在子类编写构造方法代码的时候,需要用到父类的构造方法super(name, age)。父类没有会报错的。

抽象类的子类常规情况是重写抽象类的方法,而不是让子类也是抽象类。

编写带有抽象类的标准Javabean类

青蛙frog ;属性:名字,年龄;行为:吃虫子,喝水
狗Dog;属性:名字,年龄;行为:吃骨头,喝水
山羊Sheep;属性:名字,年龄;行为:吃草,喝水

Frog
+eat()
Dog
+eat()
Sheep
+eat()
Animal
- String name
- int age
+drink()
+eat()
package myabstract.a02abstractdemo2;public class Test {public static void main(String[] args) {Frog f = new Frog("小绿", 1);System.out.println(f.getName() + ", " + f.getAge());f.eat();f.drink();}
}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 void drink(){System.out.println("动物在喝水");}public abstract void eat();
}class Dog extends Animal{public Dog() {}public Dog(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println("狗在吃骨头");}
}class Frog extends Animal{public Frog() {}public Frog(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println("青蛙在吃虫子");}
}class Sheep extends Animal{public Sheep() {}public Sheep(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println("山羊在吃草");}
}

接口

接口的介绍、引入

Frog
+eat()
Dog
+eat()
Rabbit
+eat()
Animal
- String name
- int age
+drink()
+eat()

还是上面的例子,现在我想加入一个游泳的方法。狗和青蛙会游泳,但是兔子不会。因此游泳不可能写在父类了,兔子继承了那岂不是出bug了。

在狗和青蛙类里面定义游泳的方法,又出现了分支。swim和swimming都是游泳的意思,一个人开发能做到统一,但是一个团队呢。就要制定规则。而且排除这个例子,很多动物会游泳,很多不会。难不成复制和不复制?这就是接口解决的事情。

因此把游泳这个功能定义成一个接口,抽象的方法。做到了统一。

  public abstract void swim();

接口:就是一种规则

老师是这么举例子的,比如说搬家,搬东西。一开始可以定义成一个方法。

public void 搬家(Car c){}

只要把Car的对象传过去,那就可以实现搬家。

但是搬家并不是只能用车搬家。我可以找搬家公司,我可以用三轮车。我也可以肩扛。

问题来了,搬家公司、三轮车、肩扛都不会继承于Car类。那这个方法怎么用呢、

我现在需要什么,我不需要一个完美的继承体系,而是能搬家就行,完成这个工作。怎么干我不管,我只在乎把这件事完成。

因此可以定义一个运输的接口

public interface 运输 {...
}

然后在搬家的方法中直接写接口的类型

public void 搬家(运输的接口 c){}

因此调用方法的时候搬家(车)还是搬家(搬家公司)都可以实现

接口不代表一类事物,而是一种规则,是对行为的抽象

接口和抽象类的区别。接口也是抽象方法。

类是一类事物,接口是一种规则。抽象类作为父类,是有继承的。如果把游泳的方法定义在抽象类里面,那么兔子会继承,以后猫也会继承。鸟也会继承。但是他们不会游泳的。不符合现实。

定义成接口。青蛙、狗,以后还会有鱼等等都会游泳,那我就直接用游泳的这个规则。而且学生、老师是不是都会游泳。虽然人和动物是两个不相关的类。但是接口,规则。人暗战这个规则就行了。

接口的定义和使用

  • 接口用关键字interface来定义

    public interface 接口名 {}

  • 接口不能实例化

  • 接口和类之间是实现关系,通过implements关键字表示

    public class 类名 implements 接口名 {}

  • 接口的子类(实现类)

    要么重写接口中的所有抽象方法

    要么是抽象类

  • 注意1:接口和类的实现关系,可以单实现,也可以多实现。

    public class 类名 implements 接口名1,接口名2 {}

  • 注意2:实现类还可以在继承一个类的同时实现多个接口。

    public class 类名 extends 父类 implements 接口名1,接口名2 {}

老师的ppt,一大堆好复杂,接口的定义public interface 接口名 {},实现这个接public class 类名 implements 接口名 {},一个类可以实现多个接口public class 类名 implements 接口名1,接口名2 {},还有更复杂的public class 类名 extends 父类 implements 接口名1,接口名2 {}

接下来把上面的例子做一下。熟悉一个接口的语法

public class Test {public static void main(String[] args) {Frog f = new Frog("小绿", 1);System.out.println(f.getName() + ", " + f.getAge());f.eat();f.drink();f.siwm();Sheep s = new Sheep("小羊", 2);System.out.println(s.getName() + ", " + s.getAge());s.eat();s.drink();}
}interface Siwm {public abstract void siwm();
}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 void drink(){System.out.println("动物在喝水");}public abstract void eat();
}class Dog extends Animal implements Siwm{public Dog() {}public Dog(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println("狗在吃骨头");}@Overridepublic void siwm() {System.out.println("狗在游泳");}
}class Frog extends Animal implements Siwm{public Frog() {}public Frog(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println("青蛙在吃虫子");}@Overridepublic void siwm() {System.out.println("青蛙在游泳");}
}class Sheep extends Animal {public Sheep() {}public Sheep(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println("山羊在吃草");}
}

代码的逻辑很简单。略过了

接口中的成员特点

成员变量

只能是常量

默认修饰符:public static final

多个子类的共有属性会抽取到父类。因此在接口中不会出现name,age等变量。而且接口属于一种规则,规则不能发生改变。

接口里面的变量都是常量,用final修饰。不能改变。static是方便通过类名.成员变量的方式调用。

默认的,那就是即使不写,Java也会这么规定。

成员方法

只能是抽象方法

默认修饰符:public abstract

构造方法

这里老师用Java内存分析工具做的,我果断放弃了。

编写带有接口和抽象类的标准Javabean类

我们现在有乒乓球运动员和篮球运动员,乒乓球教练和篮球教练。为了出国交流,跟乒乓球相关的人员都需要学习英语。请用所有知识分析,在这个案例中,哪些是具体类,哪些是抽象类,哪些是接口?

  • 乒乓球运动员:姓名,年龄,学打乒乓球,说英语
  • 篮球运动员:姓名,年龄,学打篮球
  • 乒乓球教练:姓名,年龄,教打乒乓球,说英语
  • 篮球教练:姓名,年龄,教打篮球
public class Test {public static void main(String[] args) {PingPongSportsman pps = new PingPongSportsman("张三", 22);System.out.println(pps.getName() + ", " + pps.getAge());pps.study();pps.speakEnglish();BasketballCoach bc = new BasketballCoach("老张", 46);System.out.println(bc.getName() + ", " + bc.getAge());bc.teach();}
}// 不希望人被实例化,设置为抽象类
abstract 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;}
}interface English {public abstract void speakEnglish();
}abstract class Coach extends Person{public Coach() {}public Coach(String name, int age) {super(name, age);}public abstract void teach();
}abstract class Sportsman extends Person {public Sportsman() {}public Sportsman(String name, int age) {super(name, age);}public abstract void study();
}class PingPongSportsman extends Sportsman implements English{public PingPongSportsman() {}public PingPongSportsman(String name, int age) {super(name, age);}@Overridepublic void speakEnglish() {System.out.println("乒乓球运动员" + super.getName() + "正在说英语");}@Overridepublic void study() {System.out.println("乒乓球运动员" + super.getName() + "正在练习打乒乓球");}
}class BasketballSportsman extends Sportsman{public BasketballSportsman() {}public BasketballSportsman(String name, int age) {super(name, age);}@Overridepublic void study() {System.out.println("篮球运动员" + super.getName() + "正在练习打篮球");}
}class BasketballCoach extends Coach{public BasketballCoach() {}public BasketballCoach(String name, int age) {super(name, age);}@Overridepublic void teach() {System.out.println("篮球教练" + super.getName() + "正在教运动员打篮球");}
}class PingPongCoach extends Coach implements English{public PingPongCoach() {}public PingPongCoach(String name, int age) {super(name, age);}@Overridepublic void teach() {System.out.println("乒乓球教练" + super.getName() + "正在教运动员打乒乓球");}@Overridepublic void speakEnglish() {System.out.println("乒乓球教练" + super.getName() + "正在说英语");}
}

代码挺长的,但是逻辑很基础。

多学三招

JDK8开始接口新增的方法

  • JDK7以前:接口中只能定义抽象方法。
  • JDK8的新特性:接口中可以定义有方法体的方法。(默认、静态)
  • JDK9的新特性:接口中可以定义私有方法

光看这些特性,我也不懂。

有这么一个场景。定义了一个接口。

interface Inter {public abstract void method();
}

然后去实现。也实现了。

class InterImpl implements Inter{public void method(){...}
}

但是过几天,我又在Inter接口里面添加了一个新的规则、那么InterImpl瞬间报错。因为每个抽象方法都要被重写。如果不重写,会报错。

如何避免这个问题了,那就是定义有方法体的方法,就像JDK8和JDK9描述

interface Inter {public abstract void method();定义有方法体的方法
}

接口中有方法体的方法是在接口升级时为了兼容性而使用的

接口升级,做项目会有版本,1.0,2.0,可能你会在1.0的时候定义了一个接口,当然也实现了。然后在2.0时候,你把这个接口丰富了一下,添加了10个方法。如果是之前,实现类会直接报错。但是有了JDK8和JDK9的新特性。那么就不会报错了

此时实现类就不需要立马修改了,等以后用到某个规则了,再重写就行了

在JDK8以后接口中新增的默认方法
  • 允许在接口中定义默认方法,需要使用关键字default修饰

    作用:解决接口升级的问题

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

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

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

  • 默认方法不是抽象方法,所以不强制被重写。但是如果被重写,重写的时候去掉default关键字
  • public可以省略,default:不能省略
  • 如果实现了多个接口,多个接口中存在相同名字的默认方法,子类就必须对该方法进行重写
package myinterface.a03myinterfacedemo3;public interface Inter {// 默认方法public default void show(){System.out.println("接口中的默认方法show()");}// 抽象方法public abstract void method();
}

以上代码定义了一个默认方法和抽象方法。注意default和abstract的用法。接着去实现这个接口,并去创建对象

package myinterface.a03myinterfacedemo3;public class Test {public static void main(String[] args) {InterImpl ii = new InterImpl();ii.method();ii.show();}
}class InterImpl implements Inter {@Overridepublic void method() {System.out.println("实现类重写了抽象方法");}
}

在之前,代码肯定是有问题的,因为你要重写接口里的全部方法,但是这一次代码可以运行

D:\Develop\Java\jdk-17\bin\java.exe "-javaagent:D:\Develop\JetBrains\IntelliJ IDEA Community Edition 2024.1.2\lib\idea_rt.jar=56549:D:\Develop\JetBrains\IntelliJ IDEA Community Edition 2024.1.2\bin" -Dfile.encoding=UTF-8 -classpath "E:\yang\专业\Java Code\basic-code\out\production\day13" myinterface.a03myinterfacedemo3.Test
实现类重写了抽象方法
接口中的默认方法show()Process finished with exit code 0

也可以直接重写这个默认方法

package myinterface.a03myinterfacedemo3;public class Test {public static void main(String[] args) {InterImpl ii = new InterImpl();ii.method();ii.show();}
}class InterImpl implements Inter {@Overridepublic void method() {System.out.println("实现类重写了抽象方法");}@Overridepublic void show() {System.out.println("实现类重写了默认方法");}
}
D:\Develop\Java\jdk-17\bin\java.exe "-javaagent:D:\Develop\JetBrains\IntelliJ IDEA Community Edition 2024.1.2\lib\idea_rt.jar=56583:D:\Develop\JetBrains\IntelliJ IDEA Community Edition 2024.1.2\bin" -Dfile.encoding=UTF-8 -classpath "E:\yang\专业\Java Code\basic-code\out\production\day13" myinterface.a03myinterfacedemo3.Test
实现类重写了抽象方法
实现类重写了默认方法Process finished with exit code 0
在JDK8以后接口中新增的静态方法

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

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

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

  • 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用;
  • public可以省略,static不能省略

在接口中定义一个静态方法

package myinterface.a04myinterfacedemo4;public interface Inter {// 抽象方法public abstract void method();// 静态方法public static void show(){System.out.println("接口中的静态方法show()");}
}

还是去实现这个接口去看怎么使用。

package myinterface.a04myinterfacedemo4;public class Test {public static void main(String[] args) {// 调用接口中的静态方法Inter.show();  // 接口中的静态方法show()}
}

因为接口中的静态方法不能通过实现类去重写,也不能通过实现类去调用,因此完全不用写实现类,直接在测试类中接口名.静态方法()的形式使用

在JDK9以后接口中新增的私有方法

这种方法是不允许外界访问的。

接口中私有方法的定义格式:

  • 格式1:private 返回值类型 方法名 (参数列表) {}

范例1:private void show() {}

  • 格式2:private static 返回值类型 方法名 (参数列表) {}

范例1:private static void show() {}

普通私有方法的使用方式

package myinterface.a05myinterfacedemo5;public interface Inter {public default void method1(){System.out.println("method1()方法开始执行了");log();}// 普通的私有方法,给默认方法服务private void log(){System.out.println("正在监控程序运行状态,这里有100行代码");}}

静态的私有方法

package myinterface.a05myinterfacedemo5;public interface Inter {public static void method1(){System.out.println("method1()方法开始执行了");log();}// 静态的私有方法,给静态方法服务private static void log(){System.out.println("正在监控程序运行状态,这里有100行代码");}}

通过代码,记住两句话就好。也写在注释里了

普通的私有方法,给默认方法服务

静态的私有方法,给静态方法服务

混用是不行的,idea会报错。标红

接口的应用

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

第一种,在接口的引入和介绍中已经了解到了。第二种,接口的多态第一次听说

package myinterface.a06myinterfacedemo6;public class Test {public static void main(String[] args) {// 使用接口类型的引用指向不同的实现类对象Animal myDog = new Dog();Animal myCat = new Cat();// 调用makeSound方法,多态性使得我们可以使用同一个接口引用调用不同实现类的实现myDog.makeSound(); // 输出: Woof!myCat.makeSound(); // 输出: Meow!//        Animal mySheep = new Sheep();  // java: 不兼容的类型: }
}// 定义一个接口
interface Animal {void makeSound();
}// 定义两个实现类
class Dog implements Animal {public void makeSound() {System.out.println("Woof!");}
}class Cat implements Animal {public void makeSound() {System.out.println("Meow!");}
}class Sheep{public void makeSound() {System.out.println("baa!");}
}

在这个例子中,定义了一个名为Animal的接口,它有一个makeSound方法。然后,创建了两个实现Animal接口的实现类:DogCat。在Test方法中,使用接口 对象名 = new 实现对象()的方式创建了DogCat对象。当我们调用myAnimal.makeSound()时,会根据实际指向的对象类型执行相应的方法。这就是接口多态的体现。

执行 Animal mySheep = new Sheep();会报错,因为Sheep没有实现Animal的接口

E:\yang\专业\Java Code\basic-code\day13\src\myinterface\a06myinterfacedemo6\Test.java:13:26
java: 不兼容的类型: myinterface.a06myinterfacedemo6.Sheep无法转换为myinterface.a06myinterfacedemo6.Animal

适配器设计模式

  • 设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。

    使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。

    简单理解:设计模式就是各种套路。

设计模式在编程界很常听到。按我现在的理解就是一种代码规范。

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

假设我现在写了一个接口,里面有5个抽象方法

package myinterface.a07myinterfacedemo7;public interface Inter {public abstract void method1();public abstract void method2();public abstract void method3();public abstract void method4();public abstract void method5();
}

接下来就是实现接口。但是我只想用第三个抽象方法method3()

实现接口,按照前面学到的,要重写所以的抽象方法。

package myinterface.a07myinterfacedemo7;public class InterImpl implements Inter{@Overridepublic void method1() {}@Overridepublic void method2() {}@Overridepublic void method3() {}@Overridepublic void method4() {}@Overridepublic void method5() {}
}

我的初衷是我想实现接口里的method3方法。虽然可以对其他方法进行空实现。这种方法好是好,但却影响代码阅读性。

这个时候用到了适配器设计模式。在接口文件Inter和实现类文件InterImpl的中间编写一个类InterAdapter让这个类去实现接口

package myinterface.a07myinterfacedemo7;public abstract class InterAdapter implements Inter{@Overridepublic void method1() {}@Overridepublic void method2() {}@Overridepublic void method3() {}@Overridepublic void method4() {}@Overridepublic void method5() {}
}

还是空实现。因为外界创建这个InterAdapter类是没有意义的,所以就可以用abstract来修饰,

接下来,让InterImpl去继承InterAdapter。在这个类里面,我需要用到什么方法,就去重写什么方法。

package myinterface.a07myinterfacedemo7;public class InterImpl extends InterAdapter{@Overridepublic void method3() {System.out.println("我需要使用method3方法");}
}

然后再写个测试类,去使用。

package myinterface.a07myinterfacedemo7;public class Test {public static void main(String[] args) {InterImpl ii = new InterImpl();ii.method3();  // 我需要使用method3方法}
}

对于这个设计模式。主要是提高了代码的阅读性。如果把空实现和实现都写到一起。代码不报错。但是影响代码阅读性。

内部类

内部类介绍

内部类是类的五大成员之一,而类的五大成员除了内部类,其他都学过,有属性、方法、构造方法和代码块。

内部类,在类的里面在定义一个类。

public class Outer {public class Inner {}
}

这里又学到了一个名词,外部其他类。外部其他类就是之前写过的测试类、JavaBean类以及实现类。

Outer是外部类,Inner是内部类。

有这么一个需求,定义一个JavaBean类用于类描述汽车。其中属性:汽车的品牌,车龄,颜色,发动机的品牌,使用年限。

这个很简单啊

public class Car{String carName;int carAge;int carColor;String engineName;int engineAge;
}

根据以前的知识,咱们都会这么写。

面向对象是对实现世界的建模,发动机是一个个体,与车并没有很大的关系。那就把发动机单独写成一个类。可单独一个发动机并没有什么意义。因此最好是把发动机定义成内部类。汽车类的里面。

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

内部类的访问特点

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

接下来,定义一个内部类

package myinnerclass.a01innerclassdemo1;public class Car {String CarName;int CarAge;String CarColor;class Engine {String engineName;int engineAge;}    
}

上面是内部类的定义方式

package myinnerclass.a01innerclassdemo1;public class Car {private String carName;int CarAge;String CarColor;class Engine {String engineName;int engineAge;public void show(){System.out.println(engineName);System.out.println(carName);}}}

内部类可以直接访问外部类的成员,即使是私有。

package myinnerclass.a01innerclassdemo1;public class Car {private String carName;int CarAge;String CarColor;public void show(){Engine e = new Engine();  // 创建内部类对象System.out.println(this.carName);System.out.println(e.engineName);}class Engine {String engineName;int engineAge;public void show(){System.out.println(engineName);System.out.println(carName);}}}

外部类访问内部类必须创建内部类对象,否则idea会标红,也会报错

E:\yang\专业\Java Code\basic-code\day13\src\myinnerclass\a01innerclassdemo1\Car.java:11:28
java: 找不到符号符号:   变量 engineName位置: 类 myinnerclass.a01innerclassdemo1.Car

因为成员变量engineName不在Car里面,this.engineName是找不到的

内部类的应用,老师带我们看了一段关于ArrayList的源码

public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{...private class Itr implements Iterator<E> {int cursor;       // index of next element to returnint lastRet = -1; // index of last element returned; -1 if no suchint expectedModCount = modCount;...}...
}

Itr就是定义在ArrayList里面的内部类。用于对数组的迭代。遍历数组。

Itr类不能单独出现,因为没有集合,也就不可能遍历集合。因此Java就是这么设计的。

有两个事物,A和B,事物B是事物A的一部分,而且事物B单独存在没有意义,那么就要把事物A定义成外部类,把事物B定义成内部类

内部类的分类

成员内部类

写在成员的位置,属于外部类的成员

成员内部类可以被一些修饰符所修饰,比如:private、默认、protected、public、static等

在成员内部类里面,JDK16之前不能定义静态变量,JDK16开始才可以定义静态变量。

package myinnerclass.a01innerclassdemo1;public class Car {String CarName;int CarAge;String CarColor;class Engine {String engineName;int engineAge;}    
}

刚刚写的代码,就是一个标准的成员内部类。

获取成员内部类对象的两种方式

package myinnerclass.a02innerclassdemo2;public class Outer {class Inner{}
}

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

package myinnerclass.a02innerclassdemo2;public class Test {public static void main(String[] args) {Outer.Inner oi = new Outer().new Inner();}
}

这个就相当于链式调用。

如果成员内部类被private修饰,那么就不能被外界访问。这个时候就用到了方法二

package myinnerclass.a02innerclassdemo2;public class Outer {private class Inner{}public Inner getInstance(){return new Inner();}
}

方法二:外部类编写方法,对外提供内部类对象

package myinnerclass.a02innerclassdemo2;public class Test {public static void main(String[] args) {
//        Outer.Inner oi = new Outer().new Inner();Outer outer = new Outer();Object inner = outer.getInstance();System.out.println(outer.getInstance());  // myinnerclass.a02innerclassdemo2.Outer$Inner@41629346}
}

这里是个小细节,通过外部类编写方法调用内部类对象时,outer.getInstance();的类型就不是Outer.Inner了、而是他的父类。因为Inner类没有继承关系,所以就用了默认父类Object

也可以直接使用 System.out.println(outer.getInstance());

在jdk16之前,在内部类中定义静态变量会报错。因为我现在是jdk17,我在jdk17中定义静态变量不会报错

package myinnerclass.a02innerclassdemo2;public class Outer {private class Inner{static int a = 10;}
}

在idea中可以切换jdk版本。

在菜单File中下面选择Project Structure,快捷键是Ctrl+Alt+Shift+S,在项目设置Project Settings中选择Project,右边

  • SDK:表示当前项目安装的是哪个jdk;
  • Language level:表示用哪个版本编译项目。这个选择必须要小于或者等于安装的jdk版本,默认选择是SDK default,与安装版本一致。

修改jdk版本,也就是修改Language level。上面说16开始才可以定义静态变量,那我改成15

idea会在static出报红,提示信息:Static declarations in inner classes are not supported at language level ‘15’

有一个关于内部类的面试题

package myinnerclass.a03innerclassdemo3;import java.util.PropertyResourceBundle;public class Outer {int a = 10;class Inner{private int a = 30;private void show(){int a = 30;
//            System.out.println(???);  // 10
//            System.out.println(???);  // 20
//            System.out.println(???);  // 30}}public Inner getInstance(){return new Inner();}
}

问号里面应该怎么写

难点在于方法里的成员变量、内部类成员变量和外部类成员变量都重名了,如果不重名,

内部类可以直接访问外部类的成员,包括私有

System.out.println(Outer.this.a);  // 10
System.out.println(this.a);  // 20
System.out.println(a);  // 30

体现了一点就近原则

看一下完整的代码

package myinnerclass.a03innerclassdemo3;public class Test {public static void main(String[] args) {Outer.Inner oi = new Outer().new Inner();oi.show();}
}class Outer {int a = 10;class Inner{private int a = 30;void show(){int a = 30;System.out.println(Outer.this.a);  // 10System.out.println(this.a);  // 20System.out.println(a);  // 30}}public Inner getInstance(){return new Inner();}
}

通过内存图分析这段代码,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

线有点乱,不过能看

  • 外部类和内部类在内存中是两个独立的字节码文件。这个结论可以在本地文件中验证,打开项目的根目录,会有一个out目录,慢慢找会发现有两个文件分别是外部类字节码文件Outer.class和内部类字节码文件Outer$Inner.class
  • 在堆内存中会开辟外部类和内部类的内存空间。而且虚拟机会给Imner的内存对象添加一个隐藏的成员变量Outer.this,用于记录外部类对象的地址值。
  • Outer.Inner oi = new Outer().new Inner();中的oi记录的是内部类的地址值002oi.shou()的调用者是002,sout(a)用的是就近原则,是方法里面的变量a,sout(this.a)是对象002的变量a,而Outer.this.a,因为Outer.this的值是001,所以找地址值是001的变量a

静态内部类

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

package myinnerclass.a01innerclassdemo1;public class Car {String CarName;int CarAge;String CarColor;static class Engine {String engineName;int engineAge;}    
}

静态内部类就是在成员内部类的基础上加了一个static修饰。循环咱们之前学习过的一个原则是静态只能访问静态。

  • 创建静态内部类对象的格式:外部类名.内部类名 对象名 = new 外部类名.内部类名();
  • 调用非静态方法的格式:先创建对象,用对象调用
  • 调用静态方法的格式:外部类名.内部类名.方法名():
package myinnerclass.a04innerclassdemo4;public class Outer {int a = 10;static int b = 20;static class Inner{public void show1(){System.out.println(a);  // 标红System.out.println(b);}static public void show2(){System.out.println(a);  // 标红System.out.println(b);}}}

只能访问被static修饰的外部类成员变量

package myinnerclass.a04innerclassdemo4;public class Outer {int a = 10;static int b = 20;static class Inner{public void show1(){// 创建对象访问外部类变量Outer outer = new Outer();System.out.println(outer.a);  System.out.println(b);}static public void show2(){// 创建对象访问外部类变量Outer outer = new Outer();System.out.println(outer.a);System.out.println(b);}}
}

需要在内部类中创建外部类的变量,才能访问非静态内容

package myinnerclass.a04innerclassdemo4;public class Test {public static void main(String[] args) {// 创建静态内部类对象Outer.Inner oi = new Outer.Inner();// 访问非静态方法oi.show1();  // 非静态方法被调用了// 访问静态方法
//        oi.show2();  // 静态方法被调用了Outer.Inner.show2();  // 静态方法被调用了}
}class Outer {static class Inner{public void show1(){System.out.println("非静态方法被调用了");}static public void show2(){System.out.println("静态方法被调用了");}}
}

创建静态内部类,以及调用方法、调用非静态方法必须是对象名.方法名,而调用静态方法有两种方式:对象名.方法名外部类名.内部类名.方法名():,但是强推外部类名.内部类名.方法名():

局部内部类

  • 将内部类定义在方法里面就叫做局部内部类,类似于方法里面的局部变量。
  • 外界是无法直接使用,需要在方法内部创建对象并使用。
  • 该类可以直接访问外部类的成员,也可以访问方法内的局部变量。
package myinnerclass.a05innerclassdemo5;public class Outer {public void show(){class Inner{}}
}

局部内部类的定义方式,局部内部类的地位和方法里局部变量一样。能修饰局部变量的关键字同样也可以修饰局部内部类。

package myinnerclass.a05innerclassdemo5;public class Test {public static void main(String[] args) {Outer outer = new Outer();outer.show();}
}class Outer {int a = 10;public void show(){int b = 20;class Inner{String name;int age;public void method1(){System.out.println(a);System.out.println(b);System.out.println("非静态方法method1");}public static void method2(){System.out.println("静态方法method2");}}// 创建局部内部类对象Inner inner = new Inner();System.out.println(inner.name);System.out.println(inner.age);inner.method1();Inner.method2();}
}

这个的运行结果是

D:\Develop\Java\jdk-17\bin\java.exe "-javaagent:D:\Develop\JetBrains\IntelliJ IDEA Community Edition 2024.1.2\lib\idea_rt.jar=52143:D:\Develop\JetBrains\IntelliJ IDEA Community Edition 2024.1.2\bin" -Dfile.encoding=UTF-8 -classpath "E:\yang\专业\Java Code\basic-code\out\production\day13" myinnerclass.a05innerclassdemo5.Test
null
0
10
20
非静态方法method1
静态方法method2

这个用的不多,了解一下即可。注意局部内部类不能在测试类里面创建,

匿名内部类

匿名,那就是没有名字。没有名字的内部类。格式如下

new 类名或者接口名(){重写方法;
};

举例:

package myinnerclass.a06innerclassdemo6;public class Test {public static void main(String[] args) {new Swim(){@Overridepublic void swim() {System.out.println("重写了这个方法");}};}
}interface Swim {public abstract void swim();
}

在测试类中创建了一个匿名内部类。有没有想过,匿名内部类是那一部分。换句话说,这段代码能拆解、

// 没有名字的类
{@Overridepublic void swim() {System.out.println("重写了这个方法");}
};

上面就是没有名字的类,匿名类。匿名类实现了Swim()接口、实现接口,那就要在重写方法,方法写在了匿名类中。new的是没有名字的类,匿名类。

package myinnerclass.a06innerclassdemo6;public class Test {public static void main(String[] args) {new Animal(){@Overridepublic void eat() {System.out.println("重写eat方法");}};}
}
abstract class Animal {public abstract void eat();
}

上面的Swim是实现,这里的Animal是继承。其他都是一样的。

匿名类虽然没有名称,但是会在本地目录产生字节码文件。因为我创建了两个匿名类,所以会在本地out目录下产生两个匿名类的字节码文件Test$1.classTest$2.class。可以得出虚拟机会对匿名类进行命名外部类名$序号

这里多学一招,反编译。把字节码文件变成我们能看懂的文件

在当前目录下打开cmd命令行工具,Javap进行反编译

PS ...\yinnerclass\a06innerclassdemo6> javap '.\Test$1.class'
Compiled from "Test.java"
class myinnerclass.a06innerclassdemo6.Test$1 implements myinnerclass.a06innerclassdemo6.Swim {myinnerclass.a06innerclassdemo6.Test$1();public void swim();
}

javap '.\Test$1.class'是对Test$1.class进行反编译

class myinnerclass.a06innerclassdemo6.Test$1 implements myinnerclass.a06innerclassdemo6.Swim class myinnerclass.a06innerclassdemo6.Test$1是类名,implements myinnerclass.a06innerclassdemo6.Swim 实现这个接口

myinnerclass.a06innerclassdemo6.Test$1();是Java虚拟机提供的默认无参构造。public void swim();是Swim接口里的方法

package myinnerclass.a06innerclassdemo6;public class Test {public static void main(String[] args) {// 以前写法// Dog d = new Dog();// method(d);// 匿名内部类的用法method(new Animal(){@Overridepublic void eat() {System.out.println("重写eat方法");}});  // 重写eat方法}public static void method(Animal a){  // 相当于Animal a = 子类对象a.eat();}
}abstract class Animal {public abstract void eat();
}

这么写的好处是Dog类我只用一次,就不能创建Dog的Java文件了、

package myinnerclass.a06innerclassdemo6;public class Test2 {public static void main(String[] args) {// 接口的多态Swim s = new Swim() {@Overridepublic void swim() {System.out.println("重写游泳方法");}};s.swim();  // 重写游泳方法}
}

解锁高阶玩法,这里就是接口的多态

package myinnerclass.a06innerclassdemo6;public class Test2 {public static void main(String[] args) {new Swim(){@Overridepublic void swim() {System.out.println("重写swim方法");}}.swim();  // 重写swim方法}
}

这里相当于自己调用自己。

这部分内容更像是链式编程。

总结

这部分学来好多。一边学一边忘。

学完这些。面向对象的思想应该也就是学到了。

谢谢大家阅读。有问题欢迎指正。
ss Test {
public static void main(String[] args) {
// 创建静态内部类对象
Outer.Inner oi = new Outer.Inner();

    // 访问非静态方法oi.show1();  // 非静态方法被调用了// 访问静态方法

// oi.show2(); // 静态方法被调用了
Outer.Inner.show2(); // 静态方法被调用了

}

}

class Outer {
static class Inner{

    public void show1(){System.out.println("非静态方法被调用了");}static public void show2(){System.out.println("静态方法被调用了");}
}

}


创建静态内部类,以及调用方法、调用非静态方法必须是`对象名.方法名`,而调用静态方法有两种方式:`对象名.方法名`和`外部类名.内部类名.方法名():`,但是强推`外部类名.内部类名.方法名():`### 局部内部类> * 将内部类定义在方法里面就叫做局部内部类,类似于方法里面的局部变量。
> * 外界是无法直接使用,需要在方法内部创建对象并使用。
> * 该类可以直接访问外部类的成员,也可以访问方法内的局部变量。```java
package myinnerclass.a05innerclassdemo5;public class Outer {public void show(){class Inner{}}
}

局部内部类的定义方式,局部内部类的地位和方法里局部变量一样。能修饰局部变量的关键字同样也可以修饰局部内部类。

package myinnerclass.a05innerclassdemo5;public class Test {public static void main(String[] args) {Outer outer = new Outer();outer.show();}
}class Outer {int a = 10;public void show(){int b = 20;class Inner{String name;int age;public void method1(){System.out.println(a);System.out.println(b);System.out.println("非静态方法method1");}public static void method2(){System.out.println("静态方法method2");}}// 创建局部内部类对象Inner inner = new Inner();System.out.println(inner.name);System.out.println(inner.age);inner.method1();Inner.method2();}
}

这个的运行结果是

D:\Develop\Java\jdk-17\bin\java.exe "-javaagent:D:\Develop\JetBrains\IntelliJ IDEA Community Edition 2024.1.2\lib\idea_rt.jar=52143:D:\Develop\JetBrains\IntelliJ IDEA Community Edition 2024.1.2\bin" -Dfile.encoding=UTF-8 -classpath "E:\yang\专业\Java Code\basic-code\out\production\day13" myinnerclass.a05innerclassdemo5.Test
null
0
10
20
非静态方法method1
静态方法method2

这个用的不多,了解一下即可。注意局部内部类不能在测试类里面创建,

匿名内部类

匿名,那就是没有名字。没有名字的内部类。格式如下

new 类名或者接口名(){重写方法;
};

举例:

package myinnerclass.a06innerclassdemo6;public class Test {public static void main(String[] args) {new Swim(){@Overridepublic void swim() {System.out.println("重写了这个方法");}};}
}interface Swim {public abstract void swim();
}

在测试类中创建了一个匿名内部类。有没有想过,匿名内部类是那一部分。换句话说,这段代码能拆解、

// 没有名字的类
{@Overridepublic void swim() {System.out.println("重写了这个方法");}
};

上面就是没有名字的类,匿名类。匿名类实现了Swim()接口、实现接口,那就要在重写方法,方法写在了匿名类中。new的是没有名字的类,匿名类。

package myinnerclass.a06innerclassdemo6;public class Test {public static void main(String[] args) {new Animal(){@Overridepublic void eat() {System.out.println("重写eat方法");}};}
}
abstract class Animal {public abstract void eat();
}

上面的Swim是实现,这里的Animal是继承。其他都是一样的。

匿名类虽然没有名称,但是会在本地目录产生字节码文件。因为我创建了两个匿名类,所以会在本地out目录下产生两个匿名类的字节码文件Test$1.classTest$2.class。可以得出虚拟机会对匿名类进行命名外部类名$序号

这里多学一招,反编译。把字节码文件变成我们能看懂的文件

在当前目录下打开cmd命令行工具,Javap进行反编译

PS ...\yinnerclass\a06innerclassdemo6> javap '.\Test$1.class'
Compiled from "Test.java"
class myinnerclass.a06innerclassdemo6.Test$1 implements myinnerclass.a06innerclassdemo6.Swim {myinnerclass.a06innerclassdemo6.Test$1();public void swim();
}

javap '.\Test$1.class'是对Test$1.class进行反编译

class myinnerclass.a06innerclassdemo6.Test$1 implements myinnerclass.a06innerclassdemo6.Swim class myinnerclass.a06innerclassdemo6.Test$1是类名,implements myinnerclass.a06innerclassdemo6.Swim 实现这个接口

myinnerclass.a06innerclassdemo6.Test$1();是Java虚拟机提供的默认无参构造。public void swim();是Swim接口里的方法

package myinnerclass.a06innerclassdemo6;public class Test {public static void main(String[] args) {// 以前写法// Dog d = new Dog();// method(d);// 匿名内部类的用法method(new Animal(){@Overridepublic void eat() {System.out.println("重写eat方法");}});  // 重写eat方法}public static void method(Animal a){  // 相当于Animal a = 子类对象a.eat();}
}abstract class Animal {public abstract void eat();
}

这么写的好处是Dog类我只用一次,就不能创建Dog的Java文件了、

package myinnerclass.a06innerclassdemo6;public class Test2 {public static void main(String[] args) {// 接口的多态Swim s = new Swim() {@Overridepublic void swim() {System.out.println("重写游泳方法");}};s.swim();  // 重写游泳方法}
}

解锁高阶玩法,这里就是接口的多态

package myinnerclass.a06innerclassdemo6;public class Test2 {public static void main(String[] args) {new Swim(){@Overridepublic void swim() {System.out.println("重写swim方法");}}.swim();  // 重写swim方法}
}

这里相当于自己调用自己。

这部分内容更像是链式编程。

总结

这部分学来好多。一边学一边忘。

学完这些。面向对象的思想应该也就是学到了。

谢谢大家阅读。有问题欢迎指正。

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

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

相关文章

聚芯前行|美格智能亮相2024 ChinaJoy骁龙主题馆,展现数字娱乐的无限可能

7月26日&#xff0c;2024中国国际数码互动娱乐展览会&#xff08;ChinaJoy&#xff09;在上海新国际博览中心正式拉开帷幕。美格智能携手高通公司亮相骁龙主题馆&#xff0c;以5G-A毫米波MiFi解决方案及高算力AI模组&#xff0c;共同为广大玩家和粉丝打造了一个前沿技术赋能、充…

27-《木芙蓉》

木芙蓉 木芙蓉&#xff08;Hibiscus mutabilis Linn.&#xff09;又名芙蓉花、拒霜花、木莲、地芙蓉、华木&#xff0c;原产中国。其喜温暖、湿润环境&#xff0c;不耐寒&#xff0c;忌干旱&#xff0c;耐水湿。对土壤要求不高&#xff0c;瘠薄土地亦可生长。为锦葵科、木槿属落…

校园气象站

TH-XQ3校园气象站是一个用于测量和记录气象数据的设备&#xff0c;可以帮助学生和教师更好地了解校园的气候情况。以下是校园气象站的使用方法&#xff1a; 安装&#xff1a;校园气象站通常需要安装在一个开阔的区域&#xff0c;远离建筑物和树木等遮挡物。确保气象站稳固地安装…

中文网址导航模版HaoWa1.3.1/模版网站wordpress导航主题

HaoWa v1.3.1由挖主题开发的一款网址导航类主题。 HaoWA主题除主体导航列表外&#xff0c;对主题所需的小模块都进行了开放式的HTML编辑器形式的功能配置&#xff0c;同时预留出默认的代码结构&#xff0c;方便大家在现有的代码结构上进行功能调整。 同时加入了字体图标Font …

Bus Number

https://codeforces.com/problemset/problem/991/E 假想一下&#xff0c;如果我们知道m序列的长度是不是可以计算数量 这个好算&#xff0c;但是好像多了一点数&#xff0c;因为不能有前导零&#xff0c;所以我们要减去有前导零的部分 最后得到 那么我们只需要枚举数量即可&am…

【C#】 使用GDI+获取两个多边形区域相交、非相交区域

一、使用GDI获取两个多边形区域相交、非相交区域 在 C# 中使用 GDI&#xff08;Graphics Device Interface Plus&#xff09;处理图形时&#xff0c;你可以使用 System.Drawing 和 System.Drawing.Drawing2D 命名空间中的类来操作区域&#xff08;Region&#xff09;。下面是一…

Spark累加器(Accumulator)

1.累加器类型&#xff1a; 数值累加器&#xff1a;用于计算总和、计数等。布尔累加器&#xff1a;用于计算满足特定条件的次数。自定义累加器&#xff1a;允许定义复杂的聚合逻辑和数据结构。集合累加器&#xff1a;用于计算唯一元素的数量&#xff0c;处理去重操作。 在 Spar…

Study--Oracle-07-ASM常用操作(五)

一、向磁盘组添加磁盘 1、查看系统中可用的磁盘 set lines 150; col name for a35; col path for a35; select group_number,path, state, name, total_mb, free_mb from v$asm_disk; 2、磁盘组操作 创建磁盘组 create DISKGROUP DATADGV2 EXTERNAL REDUNDANCY DISK /dev/…

解决Qt3D程序场景中无法显示创建的立体图形?

有的新手在创建Qt3D程序时&#xff0c;因为不熟练&#xff0c;导致经常遇到无法显示3D图形的情况。 原因其实也简单&#xff0c;就是设置的摄像机的位置不对&#xff0c;或者压根没有设置摄像机。 // CameraQt3DRender::QCamera *cameraEntity view.camera();cameraEntity-&g…

Java二十三种设计模式-外观模式(9/23)

外观模式&#xff1a;简化复杂系统的统一接口 引言 外观模式&#xff08;Facade Pattern&#xff09;是一种结构型设计模式&#xff0c;它为子系统中的一组接口提供一个统一的高层接口。外观模式定义了一个可以与复杂子系统交互的简化接口&#xff0c;使得子系统更加易于使用…

Android 10.0 Launcher3仿ios的folder文件夹widget功能实现二

1.前言 在10.0的系统ROM开发中,在进行一些系统Launcher3定制功能开发中,需要实现folder文件夹widget的功能,由于launcher3 默认不支持folder跨行显示,所以就需要借助自定义的widget小部件功能来实现相关功能,接下来分析实现相关功能 2.Launcher3仿ios的folder文件夹widge…

jQuery前端网页制作

1、Jquery的概述 1.1JavaScript库 JavaScript 高级程序设计(特别是对浏览器差异的复杂处理),通常很困难也很耗时。 为了应对这些调整,许多的 JavaScript (helper) 库应运而生。 这些 JavaScript 库常被称为 JavaScript 框架。 市面上一些广受欢迎的 JavaScript 框架:…

大厂linux面试题攻略五之数据库管理

一、数据库管理-MySQL语句 0.MySQL基本语句&#xff1a; 1.SQL语句-增 创建xxx用户&#xff1a; mysql>create user xxx % indentified by 123456; xxx表示用户名 %b表示该用户用来连接数据库的方式&#xff08;远程或本地连接&#xff09; indentified by 123456设置密码…

Reranker技术

文章目录 Reranker技术0. 什么是RAG1. 什么是Reranker&#xff1f;2. Reranker在RAG技术中的应用3.使用 Reranker 的优缺点4.总结参考&#xff1a;知乎 Reranker技术 0. 什么是RAG 基础 RAG 的操作流程大致如下&#xff1a;首先&#xff0c;你需要将文本切分成小段&#xff0…

centos7 docker空间不足

今天在使用docker安装镜像的时候&#xff0c;出现报错 查看原因&#xff0c;发现是分区空间不足导致的 所以考虑进行扩容 首先在vmware扩容并没有生效 因为只是扩展的虚拟空间&#xff0c;并不支持扩展分区大小&#xff0c;下面对分区进行扩容 参考&#xff1a; 分区扩容 主…

细说MCU的DAC改变输出信号频率的方法

目录 一、参考硬件 二、改变输出信号的频率 1.建立新工程 2.配置TIM3 三、代码修改 四、查看结果 一、参考硬件 本项目依赖的软件和硬件工程参考本文作者写的文章&#xff1a;细说MCU的DAC1和DAC2各自输出一通道模拟信号的方法-CSDN博客 https://wenchm.blog.csdn.net/a…

【初阶数据结构篇】二叉树算法题

文章目录 二叉树算法题前言单值二叉树相同的树对称二叉树另一棵树的子树二叉树的前序遍历 二叉树算法题 前言 本篇的算法题涉及到链式结构二叉树的实现方法可参考&#xff1a;二叉链实现方法上篇二叉链实现方法下篇 单值二叉树 如果二叉树每个节点都具有相同的值&#xff0c;…

什么情况?我代码没了

前两天检视代码时&#xff0c;发现PR里面有两个提交的描述信息一模一样&#xff0c;于是我提出应该将这两个提交合并成一个&#xff0c;保持提交树的清晰。 1 先储存起来&#xff01; 而同事这时正在开发别的特性&#xff0c;工作区不是干净的&#xff0c;没法直接执行 git r…