1 OOP特征二:继承
1.1 继承的由来
- 多个类中存在相同属性和行为的时候,将这些内容抽取到单独的一个类中,那么多个类中无需再定义这些属性和行为,只需要继承那个类即可。
-
此处的多个类称为
子类(派生类)
,单独的这个类称为父类(基类或超类)
。 -
继承描述的是事物之间的所属关系,这种关系是:
is a
的关系。例如:图中的猫属于动物,狗也属于动物。由此可见,父类更通用,子类更具体。通过继承,可以使得多种事物之间形成一种关系体系。
1.2 继承的好处
-
① 继承的出现,减少了代码的冗余(重复),提供了代码的复用性。
-
② 继承的出现,有利于功能的扩展。
-
③ 继承的出现,让类和类之间产生了关系,提供了多态的前提。
注意:不要为了获取其它类中的某个功能而去继承。
1.3 语法格式
-
在Java中,继承是通过
extends
关键字,声明一个子类继承一个父类。 -
语法:
修饰符 class 父类{
...
}
修饰符 class 子类 extends 父类 {
...
}
- 示例:
package top.open1024.inherit.demo2;
/**
* 动物
*
* @author open1024
* @version 1.0
* @since 2021-09-04 20:53
*/
public class Animal {
/**
* 姓名
*/
String name;
/**
* 年龄
*/
int age;
/**
* 吃
*/
public void eat() {
System.out.println(this.name + "在吃饭,年龄是:" + this.age);
}
}
package top.open1024.inherit.demo2;
/**
* 猫
*
* @author open1024
* @version 1.0
* @since 2021-09-04 20:46
*/
public class Cat extends Animal {
/**
* 抓老鼠
*/
public void catchMouse() {
System.out.println("抓老鼠");
}
}
package top.open1024.inherit.demo2;
/**
* 狗
*
* @author open1024
* @version 1.0
* @since 2021-09-04 20:43
*/
public class Dog extends Animal {
/**
* 看家
*/
public void lookHome() {
System.out.println("看家护院");
}
}
package top.open1024.inherit.demo2;
/**
* @author open1024
* @version 1.0
* @since 2021-09-04 20:49
*/
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
cat.name = "Jerry";
cat.age = 3;
cat.eat();
cat.catchMouse();
System.out.println("********************");
Dog dog = new Dog();
dog.name = "Tom";
dog.age = 4;
dog.eat();
dog.lookHome();
}
}
1.4 继承的特点之一:成员变量
1.4.1 父类成员变量私有化
-
父类的成员,无论是公有(public)还是私有(private),均为被子类所继承。
-
子类虽然会继承父类私有的(private)的成员,但是子类不能对继承的私有成员直接进行访问,却可以通过继承的setter和getter方法进行范访问。
- 示例:
package top.open1024.inherit.demo3;
/**
* 动物
*
* @author open1024
* @version 1.0
* @since 2021-09-04 20:53
*/
public class Animal {
/**
* 年龄
*/
int age;
/**
* 姓名
*/
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
/**
* 吃
*/
public void eat() {
System.out.println(this.name + "在吃饭,年龄是:" + this.age);
}
}
package top.open1024.inherit.demo3;
/**
* 猫
*
* @author open1024
* @version 1.0
* @since 2021-09-04 20:46
*/
public class Cat extends Animal {
/**
* 抓老鼠
*/
public void catchMouse() {
System.out.println("抓老鼠");
}
}
package top.open1024.inherit.demo3;
/**
* @author open1024
* @version 1.0
* @since 2021-09-04 20:49
*/
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
// cat.name = "Jerry"; 编译报错
cat.setName("Jerry");
cat.age = 3;
cat.eat();
cat.catchMouse();
}
}
- 在IDEA中Debug,查看对象成员变量的值,如下图所示:
1.4.2 父类和子类成员变量重名
-
子类会继承父类所有的成员变量,那么如果子类出现和父类同名的成员变量会怎么样?
-
父类代码:
package top.open1024.inherit.demo4;
/**
* 动物
*
* @author open1024
* @version 1.0
* @since 2021-09-04 20:53
*/
public class Animal {
/**
* 年龄
*/
int age = 88;
/**
* 姓名
*/
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
- 子类代码:
package top.open1024.inherit.demo4;
/**
* 猫
*
* @author open1024
* @version 1.0
* @since 2021-09-04 20:46
*/
public class Cat extends Animal {
int age = 5;
public void showAge() {
System.out.println("Cat的年龄是:" + this.age + ",Animal的年龄是:" + super.age);
}
}
- 测试代码:
package top.open1024.inherit.demo4;
/**
* @author open1024
* @version 1.0
* @since 2021-09-04 20:49
*/
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
cat.setName("Jerry");
cat.showAge();
}
}
-
总结:
-
- ① 当父类的成员变量私有化的时候,在子类中是无法直接访问的,所以是否重名没有任何影响;但是,如果想访问父类的私有成员变量,只能通过父类提供的setter和getter访问。
-
- ② 当父类的成员变量非私有化的时候,在子类中是可以直接访问的,所以如果有重名,就需要加上
super.父类成员变量名
来进行区分。
- ② 当父类的成员变量非私有化的时候,在子类中是可以直接访问的,所以如果有重名,就需要加上
注意:在实际开发中,虽然我们可以通过super关键字来实现区分父子类重名成员变量,但是不建议这么干。
1.5 继承的特点之二:成员方法
1.5.1 方法重写(override)
-
定义:在子类中可以根据需要对从父类中继承而来的方法进行改造,也称为方法的
重置
、覆盖
。在程序执行的时候,子类的方法将覆盖父类的方法。 -
要求:
-
- ① 子类重写的方法
必须
和父类被重写的方法具有相同的方法名称
、参数列表
。
- ① 子类重写的方法
-
- ② 子类重写的方法的返回值类型
不能大于
父类被重写的方法的返回值类型(这对于返回值类型为引用数据类型来说的,如果返回值类型是基本数据类型和void类型必须相同,换言之,只有继承,才会有父类和子类)。
- ② 子类重写的方法的返回值类型
-
- ③ 子类重写的方法使用的访问权限
不能小于
父类被重写的方法的访问权限(注意:子类不能重写父类中声明为privat权限的方法或final修饰的方法
)。
- ③ 子类重写的方法使用的访问权限
-
- ④ 子类方法抛出的异常不能大于父类被重写方法的异常。
注意: 子类和父类中同名同参数的方法必须同时声明为非static的(重写),或者同时声明为static的(不是重写,因为static方法是属于类的,子类无法覆盖父类的方法。
- 示例:
package top.open1024.inherit.demo5;
/**
* 手机类
*
* @author open1024
* @version 1.0
* @since 2021-09-05 13:18
*/
public class Phone {
public void sendMessage() {
System.out.println("发送短信");
}
public void call() {
System.out.println("打电话");
}
public void showNum() {
System.out.println("显示来电号码");
}
}
package top.open1024.inherit.demo5;
/**
* 智能手机
*
* @author open1024
* @version 1.0
* @since 2021-09-05 13:19
*/
public class SmartPhone extends Phone {
/**
* 重写父类的来电显示号码功能,并增加自己的显示姓名和图片功能
*/
@Override
public void showNum() {
// super.父类成员方法,表示调用父类的成员方法。
super.showNum();
// 增加自己的显示姓名和图片功能
System.out.println("显示来电姓名");
System.out.println("显示头像");
}
}
package top.open1024.inherit.demo5;
/**
* @author open1024
* @version 1.0
* @since 2021-09-05 13:25
*/
public class SmartPhoneTest {
public static void main(String[] args) {
SmartPhone smartPhone = new SmartPhone();
// 调用父类继承而来的方法
smartPhone.call();
// 调用子类重写的方法
smartPhone.showNum();
}
}
1.5.2 方法重载(overload)
- 在同一类中:
package top.open1024.inherit.demo;
/**
* @author open1024
* @version 1.0
* @since 2021-09-05 13:56
*/
public class Overload {
public int max(int a, int b) {
return a > b ? a : b;
}
public double max(double a, double b) {
return a > b ? a : b;
}
public int max(int a, int b, int c) {
return max(this.max(a, b), c);
}
}
- 在父子类中:
package top.open1024.inherit.demo;
/**
* @author open1024
* @version 1.0
* @since 2021-09-05 13:58
*/
public class Father {
public int max(int a, int b) {
return a > b ? a : b;
}
}
package top.open1024.inherit.demo;
/**
* @author open1024
* @version 1.0
* @since 2021-09-05 13:58
*/
public class Son extends Father {
public double max(double a, double b) {
return a > b ? a : b;
}
}
1.6 继承的特点之三:单继承
- ① Java只支持单继承,不支持多继承。
// 一个类只能有一个父类
class C extends A{}
- ② Java支持多层继承。
class A{}
class B extends A{}
class C extends B{}
注意:顶层父类是Object类,所有类默认继承Object类作为父类。
-
③ 子类和父类是一种相对概念。比如:B类相对于A类来说是子类,但是相对于C类来说是父类。
-
④ 一个父类可以同时有多个子类。
1.7 继承的特点之四:构造方法
-
构造方法的定义:
-
- ① 构造方法的名称和类名是一致的。
-
- ② 构造器不声明返回值类型(和声明void不同)。
-
- ③ 构造器不能被static、final、synchronized、abstract、native修饰,不能有return语句返回值。
-
- 所以,子类是无法
继承
父类的构造方法的。
- 所以,子类是无法
-
构造方法的作用:初始化实例变量的。但是,子类又会从父类那边继承所有成员变量,所以子类在初始化的过程中,必须先执行父类的初始化动作(子类的构造方法中默认有一个
super()
,表示调用父类的实例初始化方法,父类成员变量初始化后,才可以给子类使用),才能执行自己的初始化动作。
2 关键字 super
2.1 概述
-
在Java类中使用super来调用父类中的指定操作:
-
- super可用于访问父类中定义的属性。
-
- super可用于调用父类中定义的成员方法。
-
- super可用于在子类构造器中调用父类的构造器。
注意:
-
当子类和父类出现同名成员的时候,可以用super表明调用的是父类的成员。
-
super的追溯不仅限于直接父类
。 -
super和this的用法很像,this代表的是本类对象的引用,super代表的是父类的内存空间的标识。
2.2 调用父类的构造器
-
① 子类中所有的构造器
默认
都会访问父类中的无参
构造器。 -
② 当父类中没有无参构造器的时候,子类的构造器必须通过
this(参数列表)
或super(参数列表)
语句指定调用本类或父类中相应的构造器。同时,只能二选一
,并且放在构造器的首行。 -
③ 如果子类构造器既没有显示调用父类或本类的构造器,且父类中又没有无参构造器,则
编译报错
。
2.3 应用示例
- 示例:super调用方法
package top.open1024.inherit.demo6;
/**
* @author open1024
* @version 1.0
* @since 2021-09-05 15:35
*/
public class Person {
protected String name = "张三";
protected int age;
public String getInfo() {
return "姓名:" + this.name + ",年龄" + this.age;
}
}
package top.open1024.inherit.demo6;
/**
* @author open1024
* @version 1.0
* @since 2021-09-05 15:36
*/
public class Student extends Person {
private final String school = "中国社会大学";
protected String name = "李四";
@Override
public String getInfo() {
this.age = 18;
return super.getInfo() + ",学校:" + this.school;
}
}
package top.open1024.inherit.demo6;
/**
*
* @author open1024
* @version 1.0
* @since 2021-09-05 15:38
*/
public class StudentTest {
public static void main(String[] args) {
Student student = new Student();
System.out.println("student.getInfo() = " + student.getInfo());
}
}
- 示例:super调用父类的构造器
package top.open1024.inherit.demo7;
import java.util.Date;
/**
* @author open1024
* @version 1.0
* @since 2021-09-05 15:40
*/
public class Person {
private final String name;
private final int age;
private final Date birthDate;
public Person(String name, int age, Date birthDate) {
this.name = name;
this.age = age;
this.birthDate = birthDate;
}
public Person(String name, int age) {
this(name, age, null);
}
public Person(String name, Date birthDate) {
this(name, 0, birthDate);
}
public Person(String name) {
this(name, 0, null);
}
@Override
public String toString() {
return "name='" + this.name + '\'' + ", age=" + this.age + ", birthDate=" + this.birthDate;
}
}
package top.open1024.inherit.demo7;
/**
* @author open1024
* @version 1.0
* @since 2021-09-05 15:43
*/
public class Student extends Person {
private final String school;
public Student(String name, int age, String school) {
super(name, age);
this.school = school;
}
@Override
public String toString() {
return super.toString() + ", school='" + this.school;
}
}
package top.open1024.inherit.demo7;
/**
* @author open1024
* @version 1.0
* @since 2021-09-05 15:44
*/
public class StudentTest {
public static void main(String[] args) {
Student student = new Student("张三", 18, "社会大学");
System.out.println(student);
}
}
2.4 this和super的区别
区别点 | this | super |
---|---|---|
访问属性 | 访问本类中的属性,如果本类中没有找到此属性,则从父类中继续查找 | 直接访问父类中的属性 |
访问方法 | 访问本类中的方法,如果本类中没有找到此方法,则从父类中继续查找 | 直接访问父类中的方法 |
调用构造器 | 调用本类构造器,必须放在构造器的首行 | 调用父类构造器,必须放在子类构造器首行 |
3 关键字 static
3.1 概述
- 当我们在编写一个类的时候,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少个对象的情况下,
某些特定的数据在内存空间里只有一份
,例如,所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量。
3.2 类属性、类方法的设计思想
-
类属性作为该类各个对象之间的共享的变量。在设计类的时候,分析哪些属性不会因为对象的不同而改变,将这些属性设置为类属性,相应的方法设置为类方法。
-
如果方法和调用者无关,则这样的方法通常被声明为类方法,由于不需要创建对象就可以调用类方法,从而简化了方法的调用。
3.3 static的使用范围
- 在Java中,static可以修饰
属性
、方法
、代码块
、内部类
。
3.4 被static修饰的成员的特点
-
① 随着类的加载而加载。
-
② 优先于对象存在。
-
③ 修饰的成员,被所有对象所共享。
-
④ 访问权限允许的情况下,可以不创建对象,直接被类调用(推荐)。
3.5 类变量
-
类变量(类属性)由该类的所有实例共享。
-
示例:
package top.open1024.staticdemo.demo;
/**
* @author open1024
* @version 1.0
* @since 2021-09-05 21:01
*/
public class Person {
public static int total = 0;
private int id;
public Person() {
total++;
id = total;
}
}
package top.open1024.staticdemo.demo;
/**
* @author open1024
* @version 1.0
* @since 2021-09-05 21:03
*/
public class PersonTest {
public static void main(String[] args) {
System.out.println(Person.total); // 0
// 不用创建对象就可以访问静态成员
// 访问的方式:类名.类属性 类名.类方法
Person.total = 100;
System.out.println(Person.total); // 100
Person p = new Person();
System.out.println(p.total); // 101
}
}
3.6 类方法
-
没有对象的实例时,可以用类名.方法名()的形式访问由static修饰的类的方法。
-
在static方法内部只能访问类的static修饰的属性或方法,不能访问类的非static的结构(加载时机不同,类变量初始化是优先于对象的)。
-
因为不需要实例就能访问到static方法,因此static方法内部是不能有this或super的。
-
static修饰的方法不能被重写
。 -
示例:
package top.open1024.staticdemo.demo;
/**
* @author open1024
* @version 1.0
* @since 2021-09-05 21:01
*/
public class Person {
public static int total = 0;
private final int id;
public Person() {
total++;
this.id = total;
}
public static int getTotal() {
return total;
}
}
package top.open1024.staticdemo.demo;
/**
* @author open1024
* @version 1.0
* @since 2021-09-05 21:03
*/
public class PersonTest {
public static void main(String[] args) {
System.out.println(Person.total); // 0
// 不用创建对象就可以访问静态成员
// 访问的方式:类名.类属性 类名.类方法
Person.total = 100;
System.out.println(Person.total); // 100
int total = Person.getTotal();
System.out.println(total); // 100
Person p = new Person();
System.out.println(Person.total); // 101
}
}
3.7 单例模式
3.7.1 概述
-
设计模式是
在大量的实践中总结和理论化之后优选的代码结构、编程风格以及解决问题的思考方式
。设计模式免去了我们再思考和摸索,就像经典的棋谱,不同的棋局,采用不同的棋谱。 -
所谓的类的单例设计模式,就是采取一定的方法保证在整个软件系统中,对
某个类有且仅有存在一个对象实例
,并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private
,这样,就不能使用new关键字在类的外部产生类的对象了,但是类内部依然可以产生该类的对象。因为类的外部开始还无法得到类的对象,只能调用该类的某个静态方法
返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义为静态的
。
3.7.2 单例模式
- 示例:饿汉式
package top.open1024.staticdemo.demo2;
/**
* @author open1024
* @version 1.0
* @since 2021-09-06 08:02
*/
public class Singleton {
/**
* 内部提供一个当前类的实例 此实例也必须是静态化的
*/
private static final Singleton singleton = new Singleton();
// 私有化构造器
private Singleton() {
}
/**
* 提供公共的静态方法,返回当前类的对象
*
* @return {@link Singleton}
*/
public static Singleton getInstance() {
return singleton;
}
}
- 示例:懒汉式
package top.open1024.staticdemo.demo3;
/**
* 懒汉式暂时有线程安全问题
* @author open1024
* @version 1.0
* @since 2021-09-06 08:05
*/
public class Singleton {
/**
* 内部提供一个当前类的实例 此实例也必须是静态化的
*/
private static Singleton singleton;
// 私有化构造器
private Singleton() {
}
/**
* 提供公共的静态方法,返回当前类的对象
*
* @return {@link Singleton}
*/
public static Singleton getInstance() {
if (null == singleton) {
singleton = new Singleton();
}
return singleton;
}
}
3.7.3 单例模式的好处
-
由于单例模式只生成一个实例,
减少了系统性能的开销
,当一个对象的产生需要比较多的资源的时候,如:读取配置、产生其他依赖对象时,则可以通过在应用启动的时候直接产生一个实例对象,然后永久驻留内存的方式来解决。 -
举例:java.lang.Runtime
package java.lang;
import java.io.*;
import java.util.StringTokenizer;
import sun.reflect.CallerSensitive;
import sun.reflect.Reflection;
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {}
// 其他略
}
3.7.4 单例模式的应用场景
-
①
应用程序的日志应用
,一般使用单例模式实现,由于共享日志的文件一直处于打开状态,所以只能有一个实例去操作,否则内容不好追加。 -
②
数据库连接池
,一般使用单例模式实现,因为数据库连接是一种数据库资源。 -
③
读取资源文件的类
,一般使用单例模式实现,没有必要每次使用配置文件数据的时候,都生成一个对象去读取。 -
……
3.8 理解main方法的语法
-
由于Java虚拟机需要调用类的
main()
方法,所以该方法的访问权限必须是public
。 -
因为Java虚拟机执行
main()
方法的时候不需要创建对象,所以该方法必须是static
的。 -
main()
接收一个String类型的数组参数,该数组汇总保存执行Java命令时传递给所运行的类的参数。 -
因为
main()
方法是静态的,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。
4 成员变量初始化
4.1 成员变量初始化方式
4.1.1 默认赋值
类别 | 具体类型 | 默认值 |
---|---|---|
基本类型 | 整数(byte,short,int,long) | 0 |
浮点数(float,double) | 0.0 | |
字符(char) | '\u0000' | |
布尔(boolean) | false | |
数据类型 | 默认值 | |
引用类型 | 数组,类,接口 | null |
- 我们知道类中成员变量都有默认值,但是现在我们需要为成员变量赋默认值以外的值,那该怎么办?
4.1.2 显示赋值
public class Student{
public static final String COUNTRY = "中华人民共和国";
private static String school = "社会大学";
private String name;
private char gender = '男';
}
- 显示赋值,一般都是赋常量值。
4.1.3 代码块
-
如果成员变量想要初始化值不是一个硬编码的变量值,而是需要通过复杂的计算或读取文件、读取环境信息等方式才可以获取的一些值,该怎么办?
-
代码块:
为Java类或对象进行初始化
。 -
静态代码块:
-
- 写在类中方法外面的
static {}
。
- 写在类中方法外面的
-
- 为静态变量初始化。
-
- 由类加载器调用执行,每一个类的静态代码块只会执行一次,早于实例对象的创建。
修饰符 class 类名 {
// 静态代码块
static {
// 静态变量初始化
}
}
-
构造代码块(实例代码块):
-
- 写在类中方法外面的
{}
。
- 写在类中方法外面的
-
- 为实例变量初始化。
-
- 创建对象的时候,new一次,运行一次。
修饰符 class 类名 {
// 实例代码块
{
// 实例变量初始化
}
}
- 局部代码块:
写在方法内部{}
。
修饰符 class 类名 {
public void method(){
// 局部代码块
{
}
}
}
- 示例:
package top.open1024.member.args.initialization.demo1;
/**
* @author open1024
* @version 1.0
* @since 2021-09-06 16:58
*/
public class Student {
public static final String school;
static {
school = "社会大学";
System.out.println("静态代码块");
}
private final char gender = '男';
private String name;
{
this.name = "open1024";
System.out.println("构造代码块①");
}
{
this.name = "open10242";
System.out.println("构造代码块②");
}
public char getGender() {
return this.gender;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" + "gender=" + this.gender + ", name='" + this.name + '\'' + '}';
}
}
package top.open1024.member.args.initialization.demo1;
/**
* @author open1024
* @version 1.0
* @since 2021-09-06 17:01
*/
public class StudentTest {
public static void main(String[] args) {
Student student = new Student();
System.out.println("school = " + Student.school);
System.out.println("student = " + student);
Student student2 = new Student();
System.out.println("school = " + Student.school);
System.out.println("student2 = " + student2);
}
}
4.1.4 构造器
-
我们发现,显示赋值和构造器代码块为每一个实例变量初始化的都是相同的值,那么如果我们需要不同的实例对象初始化不同的值,怎么办?
-
需要使用构造器,在new对象的时候由对象的创建者决定为当前对象的实例变量赋什么值。
注意:构造器只为实例变量初始化,不为静态变量初始化。
4.2 类初始化
-
类初始化的目的:为类中的静态变量进行赋值。
-
实际上,类初始化的过程是在调用一个
<clinit>()
方法,这个方法是由编译器自动生成的。编译器会将如下的两部分的所有代码,按照顺序合并到类初始化<clinit>()
方法中。 -
- ① 静态类成员变量的显式赋值语句。
-
- ② 静态代码块中的语句。
clinit
are the static initialization blocks for the class, and static field initialization
-
整个类初始化只会进行一次,如果子类初始化的时候,发现父类没有初始化,那么就会先初始化父类。
-
示例:类的初始化不含子类
package top.open1024.classinit1.demo1;
/**
* 类的初始化
*
* @author open1024
* @version 1.0
* @since 2021-09-06 18:40
*/
public class Father {
public static int age = getNum();
private static int getNum() {
System.out.println("Father getNum");
return 10;
}
static {
System.out.println("Father ①");
}
public static int num = getNum();
static {
System.out.println("Father ②");
}
public static void show() {
System.out.println("Father show");
}
}
package top.open1024.classinit1.demo1;
/**
* @author open1024
* @version 1.0
* @since 2021-09-06 18:42
*/
public class Test {
public static void main(String[] args) {
Father.show();
System.out.println("----------");
Father.show();
}
}
运行结果
Father getNum
Father ①
Father getNum
Father ②
Father show
----------
Father show
- 示例:类的初始化包含父类和子类
package top.open1024.classinit1.demo2;
/**
* @author open1024
* @version 1.0
* @since 2021-09-07 10:22
*/
public class Father {
public static int age = getNum();
private static int getNum() {
System.out.println("Father getNum");
return 10;
}
static {
System.out.println("Father ①");
}
public static int num = getNum();
static {
System.out.println("Father ②");
}
public static void show() {
System.out.println("Father show");
}
}
package top.open1024.classinit1.demo2;
/**
* @author open1024
* @version 1.0
* @since 2021-09-07 11:05
*/
public class Son extends Father {
public static int n1 = getNum1();
private static int getNum1() {
System.out.println("Son getNum1");
return 20;
}
static {
System.out.println("Son ①");
}
public static int n2 = getNum1();
static {
System.out.println("Son ②");
}
public static void show() {
System.out.println("Son show");
}
}
package top.open1024.classinit1.demo2;
/**
* @author open1024
* @version 1.0
* @since 2021-09-07 10:46
*/
public class Test {
public static void main(String[] args) {
Son.show();
System.out.println("-----------------");
Son.show();
}
}
运行结果
Father getNum
Father ①
Father getNum
Father ②
Son getNum1
Son ①
Son getNum1
Son ②
Son show
-----------------
Son show
4.3 实例初始化
-
实例初始化的目的:为类中非静态变量赋值。
-
实际上,实例初始化的过程是在调用一个或多个
<init>()
方法,这个方法是由编译器自动生成的。
init
is the (or one of the) constructor(s) for the instance, and non-static field initialization。
-
实例初始化方法,即
<init>()
方法是由以下四部分组成: -
- ①
super()
或super(实参列表)
。
- ①
-
- ② 静态变量显示赋值代码。
-
- ③ 非静态代码块。
-
- ④ 构造器代码。
注意:② 和 ③ 是按照顺序合并的,① 一定在最前面,④ 一定在最后面。
-
实例初始化的执行特点:
-
- 创建对象的时候,才会执行。
-
- 每new一次对象,都会完成该对象的实例初始化。
-
- 调用哪个构造器,就是执行它对应的
<init>()
方法。
- 调用哪个构造器,就是执行它对应的
-
- 创建子类对象的时候,父类对应的实例初始化先执行,执行父类的那个实例初始化方法,看
super()
还是super(实参列表)
。
- 创建子类对象的时候,父类对应的实例初始化先执行,执行父类的那个实例初始化方法,看
-
示例:单个类
package top.open1024.instanceinit.demo1;
/**
* @author open1024
* @version 1.0
* @since 2021-09-07 11:47
*/
public class Father {
int age = getAge(); // 显示赋值语句
private int getAge() {
System.out.println("Father getAge");
return 10;
}
{ // 非静态代码块
System.out.println("Father ①");
}
int num = getAge(); // 显示赋值语句
public Father(){ // 构造器
System.out.println("Father 无参构造器");
}
}
package top.open1024.instanceinit.demo1;
/**
* @author open1024
* @version 1.0
* @since 2021-09-07 13:23
*/
public class Test {
public static void main(String[] args) {
Father father = new Father();
System.out.println("------------------");
Father father1 = new Father();
}
}
运行结果:
Father getAge
Father ①
Father getAge
Father 无参构造器
------------------
Father getAge
Father ①
Father getAge
Father 无参构造器
- 示例:父子类
package top.open1024.instanceinit.demo2;
/**
* @author open1024
* @version 1.0
* @since 2021-09-07 11:47
*/
public class Father {
int age = getAge(); // 显示赋值语句
private int getAge() {
System.out.println("Father getAge");
return 10;
}
{ // 非静态代码块
System.out.println("Father ①");
}
int num = getAge(); // 显示赋值语句
public Father(){ // 构造器
System.out.println("Father 无参构造器");
}
}
package top.open1024.instanceinit.demo2;
/**
* @author open1024
* @version 1.0
* @since 2021-09-07 13:52
*/
public class Son extends Father {
{
System.out.println("Son ①");
}
double height = getHeight();
private double getHeight() {
System.out.println("Son getHeight");
return 0;
}
public Son(){
System.out.println("Son 无参构造器");
}
}
package top.open1024.instanceinit.demo2;
/**
* @author open1024
* @version 1.0
* @since 2021-09-07 13:37
*/
public class Test {
public static void main(String[] args) {
Son s1 = new Son();
System.out.println("----------");
Son s2 = new Son();
}
}
运行结果:
Father getAge
Father ①
Father getAge
Father 无参构造器
Son ①
Son getHeight
Son 无参构造器
----------
Father getAge
Father ①
Father getAge
Father 无参构造器
Son ①
Son getHeight
Son 无参构造器
4.4 类初始化和实例初始化
-
类的初始化优先于实例初始化。
-
类初始化只做一次。
-
实例初始化是每次创建对象的时候都要进行。
-
示例:
package top.open1024.classandinstanceinit;
/**
* @author open1024
* @version 1.0
* @since 2021-09-07 14:18
*/
public class Father {
static {
System.out.println("Father static");
}
{
System.out.println("Father not static");
}
public Father(){
System.out.println("Father 无参构造器");
}
}
package top.open1024.classandinstanceinit;
/**
* @author open1024
* @version 1.0
* @since 2021-09-07 14:18
*/
public class Son extends Father {
static {
System.out.println("Son static");
}
{
System.out.println("Son not static");
}
public Son() {
System.out.println("Son 无参构造器");
}
}
package top.open1024.classandinstanceinit;
/**
* @author open1024
* @version 1.0
* @since 2021-09-07 14:19
*/
public class Test {
public static void main(String[] args) {
Son s1 = new Son();
System.out.println("---------------");
Son s2 = new Son();
}
}
运行结果:
Father static
Son static
Father not static
Father 无参构造器
Son not static
Son 无参构造器
---------------
Father not static
Father 无参构造器
Son not static
Son 无参构造器
5 OOP特征三:多态
5.1 概述
-
多态的前提条件:
-
- ① 有继承关系。
-
- ② 有方法重写。
-
- ③ 父类引用指向子类对象。
-
语法:
父类类型 变量名 = new 子类类名();
温馨提示:多态也可以应用在抽象类和接口上。
-
Java中的引用类型变量有两种类型:
-
- ① 编译型类型:由声明该变量所使用的类型决定的。
-
- ② 运行时类型:有实际赋予给变量的对象决定的。
-
- 换言之,
编译时,看左边;运行时,看右边
。
- 换言之,
-
如果编译时类型和运行时类型不一致,就出现了多态(对象的多态性)。
-
多态情况下,
“看左边”
:看的是父类的引用(父类中不具备子类中特有的属性和方法);“看右边”
:看的是子类的对象(实际运行的是子类重写父类的方法)。 -
一个引用类型变量如果声明为父类的类型,但是实际引用的是子类对象,那么该变量就
不能
再访问子类中添加的属性和方法,因为属性和方法是在编译时确定的,编译的时候是父类类型,没有子类独有的属性和方法。
注意:成员变量没有多态性,成员方法才具有多态性。
- 示例:
package top.open1024.polymorphism.demo1;
/**
* 动物类
*
* @author open1024
* @version 1.0
* @since 2021-09-07 14:52
*/
public class Animal {
public void eat() {
System.out.println("吃东西。。。");
}
public void sleep() {
System.out.println("动物睡觉。。。");
}
}
package top.open1024.polymorphism.demo1;
/**
* 猫
*
* @author open1024
* @version 1.0
* @since 2021-09-07 14:54
*/
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
public void catchMouse() {
System.out.println("猫捉老鼠");
}
}
package top.open1024.polymorphism.demo1;
/**
* 狗
*
* @author open1024
* @version 1.0
* @since 2021-09-07 14:53
*/
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
public void lookHome() {
System.out.println("狗看门");
}
}
package top.open1024.polymorphism.demo1;
/**
*
<pre>
* 多态的前提:
* ① 有继承关系。
* ② 有方法的重写。
* ③ 父类引用指向子类对象。
* </pre>
*
* @author open1024
* @version 1.0
* @since 2021-09-07 14:55
*/
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
cat.eat();
System.out.println("--------------------");
Dog dog = new Dog();
dog.eat();
System.out.println("--------------------");
Animal animal = new Dog();
animal.eat();
System.out.println("--------------------");
animal = new Cat();
animal.eat();
}
}
5.2 多态的应用场景
5.2.1 多态应用在形参和实参(★
)
-
父类类型作为方法的形式参数,子类对象作为实参。
-
示例:
package top.open1024.polymorphism.demo2;
/**
* 程序员
*
* @author open1024
* @version 1.0
* @since 2021-09-07 19:46
*/
public class Programmer {
public void eat() {
System.out.println("程序员吃饭");
}
}
package top.open1024.polymorphism.demo2;
/**
* 中国程序员
*
* @author open1024
* @version 1.0
* @since 2021-09-07 19:48
*/
public class ChineseProgrammer extends Programmer {
@Override
public void eat() {
System.out.println("中国程序员使用筷子吃饭");
}
public void play() {
System.out.println("中国人玩太极");
}
}
package top.open1024.polymorphism.demo2;
/**
* 印度程序员
*
* @author open1024
* @version 1.0
* @since 2021-09-07 19:49
*/
public class IndianProgrammer extends Programmer {
@Override
public void eat() {
System.out.println("印度程序员使用手吃饭");
}
public void play() {
System.out.println("印度人玩摩托车");
}
}
package top.open1024.polymorphism.demo2;
/**
* 英国程序员
*
* @author open1024
* @version 1.0
* @since 2021-09-07 19:51
*/
public class EnglishProgrammer extends Programmer {
@Override
public void eat() {
System.out.println("英国程序员用刀叉吃饭");
}
public void play() {
System.out.println("英国人玩极限运动");
}
}
package top.open1024.polymorphism.demo2;
/**
* @author open1024
* @version 1.0
* @since 2021-09-07 19:52
*/
public class Test {
public static void main(String[] args) {
ChineseProgrammer chineseProgrammer = new ChineseProgrammer();
showEat(chineseProgrammer);
IndianProgrammer indianProgrammer = new IndianProgrammer();
showEat(indianProgrammer);
EnglishProgrammer englishProgrammer = new EnglishProgrammer();
showEat(englishProgrammer);
}
private static void showEat(Programmer programmer) {
programmer.eat();
}
}
运行结果:
中国程序员使用筷子吃饭
印度程序员使用手吃饭
英国程序员用刀叉吃饭
5.2.2 多态应用在数组中
-
数组元素类型声明为父类类型,实际存储的元素是子类对象。
-
示例:
package top.open1024.polymorphism.demo3;
/**
* @author open1024
* @version 1.0
* @since 2021-09-07 20:18
*/
public class Animal {
String name;
public void showInfo() {
System.out.println("名字是:" + this.name);
}
public void eat() {
System.out.println("动物吃饭。。。。");
}
}
package top.open1024.polymorphism.demo3;
/**
* @author open1024
* @version 1.0
* @since 2021-09-07 20:20
*/
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("小猫吃饭");
}
}
package top.open1024.polymorphism.demo3;
/**
* @author open1024
* @version 1.0
* @since 2021-09-07 20:21
*/
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("小狗吃饭");
}
}
package top.open1024.polymorphism.demo3;
/**
* @author open1024
* @version 1.0
* @since 2021-09-07 20:21
*/
public class Test {
public static void main(String[] args) {
Animal[] animals = new Animal[2];
animals[0] = new Cat();
animals[1] = new Dog();
for (Animal animal : animals) {
animal.eat();
}
}
}
运行结果:
小猫吃饭
小狗吃饭
5.2.3 多态应用在方法的返回值(★
)
-
方法的返回值类型声明为父类类型,实际返回值为子类对象。
-
示例:
package top.open1024.polymorphism.demo4;
/**
* @author open1024
* @version 1.0
* @since 2021-09-07 20:35
*/
public class Animal {
public void eat() {
System.out.println("动物吃饭");
}
}
package top.open1024.polymorphism.demo4;
/**
* @author open1024
* @version 1.0
* @since 2021-09-07 20:36
*/
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("小猫吃鱼");
}
}
package top.open1024.polymorphism.demo4;
/**
* @author open1024
* @version 1.0
* @since 2021-09-07 20:36
*/
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("小狗吃肉");
}
}
package top.open1024.polymorphism.demo4;
/**
* @author open1024
* @version 1.0
* @since 2021-09-07 20:36
*/
public class Test {
public static void main(String[] args) {
Animal animal = buy("cat");
animal.eat();
animal = buy("dog");
animal.eat();
}
public static Animal buy(String name) {
if (name.equals("cat")) {
return new Cat();
} else if (name.equals("dog")) {
return new Dog();
} else {
return null;
}
}
}
运行结果:
小猫吃鱼
小狗吃肉
5.3 练习
5.3.1 练习1
-
① 声明父类Traffic,包含方法
public void drive()
。 -
② 声明子类Car,Bicycle等,并重写
drive
方法。 -
③ 在测试类的main中创建一个数组,有各种交通工具,遍历调用drive()方法。
-
示例:
package top.open1024.polymorphism.demo5;
/**
* @author open1024
* @version 1.0
* @since 2021-09-08 08:20
*/
public class Traffic {
public void drive() {
System.out.println("驱动");
}
}
package top.open1024.polymorphism.demo5;
/**
* @author open1024
* @version 1.0
* @since 2021-09-08 08:22
*/
public class Bicycle extends Traffic {
@Override
public void drive() {
System.out.println("自行车 晃荡晃荡");
}
}
package top.open1024.polymorphism.demo5;
/**
* @author open1024
* @version 1.0
* @since 2021-09-08 08:21
*/
public class Car extends Traffic {
@Override
public void drive() {
System.out.println("汽车 滴滴滴");
}
}
package top.open1024.polymorphism.demo5;
/**
* @author open1024
* @version 1.0
* @since 2021-09-08 08:23
*/
public class Test {
public static void main(String[] args) {
Traffic[] trafficArray = {new Car(), new Bicycle()};
for (Traffic traffic : trafficArray) {
traffic.drive();
}
}
}
5.3.2 练习2
-
① 声明一个父类Person类,包含方法
public void toilet()
。 -
② 声明一个子类Woman类,重写方法。
-
③ 声明一个子类Man类,重写方法。
-
④ 在测试类中声明一个方法:
public static void goToToilet(Person p){
p.toilet();
}
-
⑤ 在测试类的main方法中,创建不同子类对象,调用
goToToilet
方法进行测试。 -
示例:
package top.open1024.polymorphism.demo6;
/**
* @author open1024
* @version 1.0
* @since 2021-09-08 08:40
*/
public class Person {
public void toilet() {
System.out.println("人类洗手");
}
}
package top.open1024.polymorphism.demo6;
/**
* @author open1024
* @version 1.0
* @since 2021-09-08 08:45
*/
public class Man extends Person {
@Override
public void toilet() {
System.out.println("男人粗糙的洗手");
}
}
package top.open1024.polymorphism.demo6;
/**
* @author open1024
* @version 1.0
* @since 2021-09-08 08:45
*/
public class Woman extends Person {
@Override
public void toilet() {
System.out.println("女人细致的洗手");
}
}
package top.open1024.polymorphism.demo6;
/**
* @author open1024
* @version 1.0
* @since 2021-09-08 08:45
*/
public class Test {
public static void main(String[] args) {
Man man = new Man();
getToilet(man);
Woman woman = new Woman();
getToilet(woman);
}
public static void getToilet(Person p) {
if (null != p) {
p.toilet();
}
}
}
5.3.3 练习3
-
① 声明一个父类Employee员工类型:
-
- 有属性:姓名(String)。
-
- 有方法:
-
-
public double earning()
:用于返回实发工资,默认返回0。
-
-
-
public String getInfo()
:显示姓名和实发工资。
-
-
② 声明一个子类
SalaryEmployee
正式工,继承父类Employee: -
- 增加属性,薪资,工作日天数,请假天数。
-
- 重写方法,
public double earning()
:返回实发工资,实发工资 = 薪资 - 薪资/工作日天数 * 请假天数。
- 重写方法,
-
③ 声明一个子类
HourEmployee
小时工,继承父类Employee: -
- 有属性,工作小时数,每小时多少钱。
-
- 重写方法,
public double earning()
:返回实发工资, 实发工资 = 每小时多少钱 * 小时数。
- 重写方法,
-
④ 声明一个子类Manager经理,继承SalaryEmployee:
-
- 增加属性:奖金比例。
-
- 重写方法,
public double earning()
:返回实发工资,实发工资 = (薪资 - 薪资/工作日天数 请假天数)(1+奖金比例)。
- 重写方法,
-
⑤ 你现在是财务,需要查看每个人的实发工资,并查看工资总额:声明一个员工数组,存储各种员工,并遍历显示他们的姓名和实发工资,并计算所有员工的工资总额。
-
⑥ UML图:
- 示例:
package top.open1024.polymorphism.demo7;
/**
* 员工类
*
* @author open1024
* @version 1.0
* @since 2021-09-08 08:57
*/
public class Employee {
/**
* 姓名
*/
private String name;
/**
* 实发工资
*
* @return double 0
*/
public double earning() {
return 0;
}
/**
* 获取信息:显示姓名和实发工资
*
* @return {@link String}
*/
public String getInfo() {
return "姓名:" + this.name + ",实发工资:" + this.earning();
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
package top.open1024.polymorphism.demo7;
/**
* 正式员工
*
* @author open1024
* @version 1.0
* @since 2021-09-08 09:03
*/
public class SalaryEmployee extends Employee {
/**
* 薪水
*/
private double salary;
/**
* 工作日天数
*/
private int workingDays;
/**
* 请假天数
*/
private int leaveDays;
@Override
public double earning() {
return this.salary - this.salary / this.workingDays * this.leaveDays;
}
public double getSalary() {
return this.salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public int getWorkingDays() {
return this.workingDays;
}
public void setWorkingDays(int workingDays) {
this.workingDays = workingDays;
}
public int getLeaveDays() {
return this.leaveDays;
}
public void setLeaveDays(int leaveDays) {
this.leaveDays = leaveDays;
}
}
package top.open1024.polymorphism.demo7;
/**
* 小时工
*
* @author open1024
* @version 1.0
* @since 2021-09-08 09:07
*/
public class HourEmployee extends Employee {
/**
* 工作小时数
*/
private int workingHours;
/**
* 小时工资
*/
private double hourlyWage;
@Override
public double earning() {
return this.workingHours * this.hourlyWage;
}
public int getWorkingHours() {
return this.workingHours;
}
public void setWorkingHours(int workingHours) {
this.workingHours = workingHours;
}
public double getHourlyWage() {
return this.hourlyWage;
}
public void setHourlyWage(double hourlyWage) {
this.hourlyWage = hourlyWage;
}
}
package top.open1024.polymorphism.demo7;
/**
* 经理
*
* @author open1024
* @version 1.0
* @since 2021-09-08 09:09
*/
public class Manager extends SalaryEmployee {
/**
* 奖金比率
*/
private double bonusRatio;
@Override
public double earning() {
return super.earning() * (1 + this.bonusRatio);
}
public double getBonusRatio() {
return this.bonusRatio;
}
public void setBonusRatio(double bonusRatio) {
this.bonusRatio = bonusRatio;
}
}
package top.open1024.polymorphism.demo7;
/**
* @author open1024
* @version 1.0
* @since 2021-09-08 09:10
*/
public class Test {
public static void main(String[] args) {
SalaryEmployee salaryEmployee = new SalaryEmployee();
salaryEmployee.setName("张三");
salaryEmployee.setWorkingDays(2);
salaryEmployee.setLeaveDays(1);
salaryEmployee.setSalary(5);
HourEmployee hourEmployee = new HourEmployee();
hourEmployee.setName("李四");
hourEmployee.setWorkingHours(1);
hourEmployee.setHourlyWage(2);
Manager manager = new Manager();
manager.setName("王五");
manager.setSalary(5);
manager.setWorkingDays(2);
manager.setLeaveDays(1);
manager.setBonusRatio(0.2);
Employee[] employees = {salaryEmployee, hourEmployee, manager};
double sum = 0;
for (Employee employee : employees) {
String info = employee.getInfo();
System.out.println(info);
sum += employee.earning();
}
System.out.println("工资总额:" + sum);
}
}
5.4 向上转型和向下转型
5.4.1 概述
-
一个对象在new的时候创建哪个类型的对象,它从始到终的类型都不会变,换言之,即这个对象的运行时类型,是不会变化的。
-
将这个对象赋值给不同类型的变量,这些变量的编译型类型是不同的。
-
示例:证明对象的运行时类型不会改变
package top.open1024.polymorphism.demo8;
/**
* @author open1024
* @version 1.0
* @since 2021-09-08 10:24
*/
public class Animal {
public void eat() {
System.out.println("动物吃饭");
}
}
package top.open1024.polymorphism.demo8;
/**
* @author open1024
* @version 1.0
* @since 2021-09-08 10:25
*/
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
public void catchMouse() {
System.out.println("抓老鼠");
}
}
package top.open1024.polymorphism.demo8;
/**
* @author open1024
* @version 1.0
* @since 2021-09-08 10:25
*/
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃肉");
}
public void watchHouse() {
System.out.println("看家");
}
}
package top.open1024.polymorphism.demo8;
/**
* 证明对象的运行时状态不会发生改变
*
* @author open1024
* @version 1.0
* @since 2021-09-08 10:25
*/
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
Animal animal = cat;
Object obj = cat;
// 运行时类型
System.out.println(cat.getClass()); // class top.open1024.polymorphism.demo8.Cat
System.out.println(animal.getClass()); // class top.open1024.polymorphism.demo8.Cat
System.out.println(obj.getClass()); // class top.open1024.polymorphism.demo8.Cat
System.out.println(cat.getClass() == animal.getClass()); // true
System.out.println(obj.getClass() == animal.getClass()); // true
// cat、animal、obj的编译型类型不同
// 通过cat可以调用catchMouse中的所有方法,包括从父类继承而来的,包括自己扩展的
// 通过animal只能调用Animal类及其父类有的方法,不能调用Cat扩展的方法
// 通过obj只能调用Object类中的所有方法
}
}
-
为什么需要类型转换?
-
因为多态,一定会将子类类型的对象赋值给父类类型的引用,这个时候,
在编译期间
,就会出现类型转换的现象(向上转型,自动类型转换)。 -
但是,使用了父类类型的引用接收了子类类型的对象,我们就不能调用子类独有的方法(父类没有的方法),这个时候如果想要调用子类独有的方法,必须使用父类转换。
-
向上转型:左边的变量的类型(父类)= 右边对象/变量的类型(子类)。
-
- 此时,编译的时候按照左边的变量的类型处理,就只能调用父类中有的变量和方法了,不能调用子类独有的变量和方法了。
-
- 运行的时候,依然是子类对象的类型。
-
- 自动完成的,安全的,不会出现异常。
-
向下转型:左边的变量的类型(子类)= (子类的类型)右边对象/变量的类型(父类)。
-
- 此时,编译的时候按照左边左边的变量的类型处理,就可以调用子类独有的变量和方法了。
-
- 运行的时候,依然是子类对象的类型。
-
- 不一定安全的,需要使用(子类的类型)进行强制类型转换。
-
- 不是所有通过编译的向下类型转换都是正确的,可能会发生ClassCastException,为了安全,需要通过instanceof关键字进行判断。
- 示例:
package top.open1024.polymorphism.demo9;
/**
* @author open1024
* @version 1.0
* @since 2021-09-08 10:24
*/
public class Animal {
public void eat() {
System.out.println("动物吃饭");
}
}
package top.open1024.polymorphism.demo9;
/**
* @author open1024
* @version 1.0
* @since 2021-09-08 10:25
*/
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
public void catchMouse() {
System.out.println("抓老鼠");
}
}
package top.open1024.polymorphism.demo9;
/**
* @author open1024
* @version 1.0
* @since 2021-09-08 10:25
*/
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃肉");
}
public void watchHouse() {
System.out.println("看家");
}
}
package top.open1024.polymorphism.demo9;
/**
* @author open1024
* @version 1.0
* @since 2021-09-08 10:25
*/
public class Test {
public static void main(String[] args) {
Animal animal = new Dog();
Dog dog = (Dog)animal;
dog.eat();
dog.watchHouse();
}
5.4.2 关键字 instanceof
- 语法:
x instanceof A
- 解释:判断左边的引用类型变量x是否是右边类A的对象,如果是,返回true;否则,返回false。
注意:
-
① 要求x所在的类和类A必须是子类和父类的关系,否则编译报错。
-
② 如果x所在的类属于类A的子类,那么
x instanceof A
的返回值也为true。 -
示例:
package top.open1024.polymorphism.demo10;
/**
* @author open1024
* @version 1.0
* @since 2021-09-08 10:24
*/
public class Animal {
public void eat() {
System.out.println("动物吃饭");
}
}
package top.open1024.polymorphism.demo10;
/**
* @author open1024
* @version 1.0
* @since 2021-09-08 10:25
*/
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
public void catchMouse() {
System.out.println("抓老鼠");
}
}
package top.open1024.polymorphism.demo10;
/**
* @author open1024
* @version 1.0
* @since 2021-09-08 10:25
*/
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃肉");
}
public void watchHouse() {
System.out.println("看家");
}
}
package top.open1024.polymorphism.demo10;
/**
* instanceof 判断左边的引用类型变量是否是右边类的对象
*
* @author open1024
* @version 1.0
* @since 2021-09-08 10:25
*/
public class Test {
public static void main(String[] args) {
Animal animal = new Dog();
if (animal instanceof Dog) {
Dog dog = (Dog)animal;
dog.eat();
dog.watchHouse();
} else if (animal instanceof Cat) {
Cat cat = (Cat)animal;
cat.eat();
cat.catchMouse();
}
}
}
5.5 多态应用时关于成员变量和成员方法的原则
5.5.1 成员变量:只看编译时类型
-
如果直接访问成员变量,那么只看编译型类型。
-
示例:
package top.open1024.polymorphism.demo11;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 08:40
*/
public class Animal {
String name = "动物";
public void eat() {
System.out.println("吃饭");
}
}
package top.open1024.polymorphism.demo11;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 08:41
*/
public class Dog extends Animal {
String name = "狗";
@Override
public void eat() {
System.out.println("吃肉");
}
}
package top.open1024.polymorphism.demo11;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 08:41
*/
public class Test {
public static void main(String[] args) {
Animal animal = new Dog();
System.out.println(animal.name);
}
}
运行结果:
动物
5.5.2 非虚方法(不可以重写的方法):只看编译时类型
-
在Java中非虚方法有三种:
-
① 由
invokestatic
指令调用的static方法
,这种方法在编译的时候确定,运行的时候不会发生变化。
javap -c Test.clsss
-
② 由
invokespcial
指令调用的方法,这些方法包括私有方法
、实例构造方法
和父类方法
,这些方法也是在编译的时候确定,运行的时候不会发生变化。 -
③ 由
final
关键字修饰的方法
。虽然final方法是由invokevirtual
指令调用的,但是final修饰的方法不能够在子类中进行重写,所以final修饰的方法是不能够在运行期进行动态改变的。在Java语言规范中明确规定final方法是非虚方法。
温馨提示:静态方法不能被重写,所以最好采用类名.方法名()
的形式调用。
5.5.3 虚方法(可以重写的方法):静态分派和动态绑定
-
在Java中虚方法是指在编译阶段和类加载阶段都不能确定方法的调用入口,在运行阶段才能确定的方法,即可能被重写的方法。
-
当我们通过
对象.方法
的形式,调用一个虚方法,我们如何确定它具体执行那个方法? -
① 静态分派:先看这个对象的编译时类型,在这个对象的编译时类型中找到最匹配的方法。
最匹配指的是:实参的编译时类型和形参的类型最匹配。
-
② 动态绑定:再看这个对象的运行时类型,如果这个对象的运行时类型重写了刚刚找到的那个最匹配的方法,那么执行重写,否则依然执行刚才编译时类型的那个方法。
-
示例:没有重载有重写
package top.open1024.polymorphism.demo12;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 09:55
*/
public class Animal {
public void eat() {
System.out.println("吃饭");
}
}
package top.open1024.polymorphism.demo12;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 09:56
*/
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
package top.open1024.polymorphism.demo12;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 09:55
*/
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃肉");
}
}
package top.open1024.polymorphism.demo12;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 09:56
*/
public class Test {
public static void main(String[] args) {
Animal animal = new Cat();
animal.eat(); // 猫吃鱼
}
}
-
上面的代码在编译期间先进行静态分派:此时animal编译时类型是Animal类,所以去Animal类中搜索eat方法,如果Animal类或它的父类中没有这个方法,将会报错。
-
在运行期间动态的进行动态绑定:animal的运行时类型是Cat类,而子类Cat类重写了eat方法,所以执行的是Cat类的eat方法,如果子类Cat类没有重写eat方法,那么还是执行Animal类的eat方法。
-
示例:有重载没有重写
package top.open1024.polymorphism.demo13;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 10:02
*/
public class Father {}
package top.open1024.polymorphism.demo13;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 10:02
*/
public class Son extends Father {}
package top.open1024.polymorphism.demo13;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 10:02
*/
public class Daughter extends Father {}
package top.open1024.polymorphism.demo13;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 10:09
*/
public class MyClass {
public void method(Father father) {
System.out.println("father");
}
public void method(Son son) {
System.out.println("son");
}
public void method(Daughter daughter) {
System.out.println("daughter");
}
}
package top.open1024.polymorphism.demo13;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 10:02
*/
public class Test {
public static void main(String[] args) {
MyClass myClass = new MyClass();
Father father = new Father();
Father son = new Son();
Father daughter = new Daughter();
myClass.method(father); // father
myClass.method(son); // father
myClass.method(daughter); // father
}
}
-
上面的代码在编译期间先进行静态分派:因为myClass属于MyClass类型,那么在MyClass类中寻找最匹配的method方法。
-
在运行期间动态的进行动态绑定:确定指定的是类MyClass中的method(Father father)方法,因为此时实参类型father、son、daughter的类型都是Father类型。
-
示例:有重载没有重写
package top.open1024.polymorphism.demo14;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 10:02
*/
public class Father {}
package top.open1024.polymorphism.demo14;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 10:02
*/
public class Son extends Father {}
package top.open1024.polymorphism.demo14;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 10:02
*/
public class Daughter extends Father {}
package top.open1024.polymorphism.demo14;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 10:09
*/
public class MyClass {
public void method(Father father) {
System.out.println("father");
}
public void method(Son son) {
System.out.println("son");
}
}
package top.open1024.polymorphism.demo14;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 10:02
*/
public class Test {
public static void main(String[] args) {
MyClass myClass = new MyClass();
Father father = new Father();
Son son = new Son();
Daughter daughter = new Daughter();
myClass.method(father); // father
myClass.method(son); // son
myClass.method(daughter); // father
}
}
-
上面的代码在编译期间先进行静态分派:因为myClass属于MyClass类型,那么在MyClass类中寻找最匹配的method方法。
-
在运行期间动态的进行动态绑定:确定指定的是类MyClass中的method(Father father)和method(Son son)方法,因为此时实参类型father、son、daughter的类型分别是Father、Son、Daughter类型,而Daughter类型只能和Father参数类型匹配。
-
示例:有重载没有重写
package top.open1024.polymorphism.demo15;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 10:02
*/
public class Father {}
package top.open1024.polymorphism.demo15;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 10:02
*/
public class Son extends Father {}
package top.open1024.polymorphism.demo15;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 10:02
*/
public class Daughter extends Father {}
package top.open1024.polymorphism.demo15;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 10:09
*/
public class MyClass {
public void method(Father father) {
System.out.println("father");
}
public void method(Son son) {
System.out.println("son");
}
}
package top.open1024.polymorphism.demo15;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 10:20
*/
public class MySub extends MyClass {
public void method(Daughter daughter) {
System.out.println("daughter");
}
}
package top.open1024.polymorphism.demo15;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 10:02
*/
public class Test {
public static void main(String[] args) {
MyClass myClass = new MySub();
Father father = new Father();
Son son = new Son();
Daughter daughter = new Daughter();
myClass.method(father); // father
myClass.method(son); // son
myClass.method(daughter); // father
}
}
-
上面的代码在编译期间先进行静态分派:因为myClass属于MyClass类型,那么在MyClass类中寻找最匹配的method方法。
-
在运行期间动态的进行动态绑定:确定指定的是类MyClass中的method(Father father)和method(Son son)方法,因为此时实参类型father、son、daughter的类型分别是Father、Son、Daughter类型,而Daughter类型只能和Father参数类型匹配,在MySub类中并没有重写method(Father f)方法,所以仍然执行MyClass类中的method(Father f)方法。
-
示例:有重载有重写
package top.open1024.polymorphism.demo16;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 10:02
*/
public class Father {}
package top.open1024.polymorphism.demo16;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 10:02
*/
public class Son extends Father {}
package top.open1024.polymorphism.demo16;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 10:02
*/
public class Daughter extends Father {}
package top.open1024.polymorphism.demo16;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 10:09
*/
public class MyClass {
public void method(Father father) {
System.out.println("father");
}
public void method(Son son) {
System.out.println("son");
}
}
package top.open1024.polymorphism.demo16;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 10:20
*/
public class MySub extends MyClass {
@Override
public void method(Father father) {
System.out.println("sub--");
}
public void method(Daughter daughter) {
System.out.println("daughter");
}
}
package top.open1024.polymorphism.demo16;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 10:02
*/
public class Test {
public static void main(String[] args) {
MyClass myClass = new MySub();
Father father = new Father();
Son son = new Son();
Daughter daughter = new Daughter();
myClass.method(father); // sub--
myClass.method(son); // son
myClass.method(daughter); // sub--
}
}
-
上面的代码在编译期间先进行静态分派:因为myClass属于MyClass类型,那么在MyClass类中寻找最匹配的method方法。
-
在运行期间动态的进行动态绑定:确定指定的是类MyClass中的method(Father father)和method(Son son)方法,因为此时实参类型father、son、daughter的类型分别是Father、Son、Daughter类型,因为MySub重写了method(Father father),所以执行了MySub的method(Father father)方法。
6 关键字 native
-
natvie:本地的,原生的。
-
用法:
-
- ① 只能修饰方法。
-
- ② 表示这个方法的方法体代码不是用Java语言实现的,而是用C或C++实现的。
-
- ③ 对于Java程序员林来说,可以当做Java方法一样去正常调用,或者使用子类重写它。
-
JVM内存管理:
区域名称 | 作用 |
---|---|
程序计数器 | 程序计数器是CPU中的寄存器,它包含每一个线程下一条要执行的指令的地址 |
本地方法栈 | 当程序中调用了native的本地方法时,本地方法执行期间的内存区域 |
方法区 | 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 |
堆内存 | 存储对象(包括数组对象),new来创建的,都存储在堆内存。 |
虚拟机栈 | 用于存储正在执行的每个Java方法的局部变量表等。局部变量表存放了编译期可知长度的各种基本数据类型、对象引用,方法执行完,自动释放。 |
7 关键字final
7.1 概述
-
在Java中声明
类
、变量
和方法
时,可以使用关键字final
来修饰,表示最终的
。 -
final修饰的类不能被继承
,目的是为了提高安全性,提高程序的可读性。比如:String类
、System类
、StringBuilder类
。 -
final修饰的方法不能被子类重写
。比如:Object类的getClass()
方法。 -
final修饰的变量(成员变量或局部变量)即为常量。名称大写,且只能被赋值一次
。final修饰的成员变量必须在声明时或每个构造器中或代码块中显示赋值,然后才能使用
。
7.2 final修饰类
- 示例:
final class A {}
class B extends A{ // 错误,不能被继承}
7.3 final修饰方法
- 示例:
class A {
public final void method(){
System.out.println("A");
}
}
class B extends A {
@override
public void method(){ // 错误,不能被重写
System.out.println("B");
}
}
7.4 final修饰变量--常量
- 示例:
class A {
private final String INFO = "你好,世界";
public void method(){
// 错误,常量不能被修改
INFO = "你好,Java";
}
}
温馨提示:static final 修饰的变量被称为全局常量。
8 Object类的使用
8.1 概述
-
在Java中,Object类是所有类的根父类。
-
在Java中,如果在类的声明处没有显示的用
extends
关键字指明其父类,则默认父类为java.lang.Object
类。
8.2 toString()方法
- 方法:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
-
解释:
-
- ① 默认情况下,
toString()
方法的返回值是对象时类型 @ 对象的hashCode值的十六进制
。
- ① 默认情况下,
-
- ② 通常建议子类重写
toString()
方法。
- ② 通常建议子类重写
-
- ③ 如果我们直接使用
System.out.println(对象);
输出语句,默认会自动调用该对象的toString()
方法。
- ③ 如果我们直接使用
Java的引用数据类型的变量中实际存储的是对象的内存地址,但是Java对程序员隐藏了内存的地址信息,所以不能直接将内存地址显示出来,所以当我们打印对象的时候,JVM会帮助我们调用对象的toString()方法。
- 示例:
package top.open1024.objectdemo.demo1;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 13:11
*/
public class Person {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private int age;
/**
* 身高
*/
private double height;
public Person() {}
public Person(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
public double getHeight() {
return this.height;
}
public void setHeight(double height) {
this.height = height;
}
@Override
public String toString() {
return "Person{" + "name='" + this.name + '\'' + ", age=" + this.age + ", height=" + this.height + '}';
}
}
package top.open1024.objectdemo.demo1;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 13:13
*/
public class Test {
public static void main(String[] args) {
Person person = new Person("李白", 18, 1.8);
System.out.println(person);
String info = person.toString();
System.out.println(info);
}
}
运行结果:
Person{name='李白', age=18, height=1.8}
Person{name='李白', age=18, height=1.8}
8.3 getClass()方法
- 方法:
public final native Class<?> getClass();
-
解释:
-
- ① 该方法返回对象的运行时类型。
-
- ② 因为Java有多态特性,所以一个引用类型的变量的编译时类型和运行时类型可能不一样,所以如果需要查看一个引用类型变量实际指向对象的类型,就需要此方法了。
-
示例:
package top.open1024.objectdemo.demo2;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 13:32
*/
public class Animal {
public void eat() {
System.out.println("吃饭");
}
}
package top.open1024.objectdemo.demo2;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 13:32
*/
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
package top.open1024.objectdemo.demo2;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 13:33
*/
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃肉");
}
}
package top.open1024.objectdemo.demo2;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 13:33
*/
public class Test {
public static void main(String[] args) {
Animal animal = new Dog();
System.out.println("animal.getClass() = " + animal.getClass()); // class top.open1024.objectdemo.demo2.Dog
}
}
8.4 finalize()方法
- 方法:
protected void finalize() throws Throwable { }
-
解释:
-
- ① 当对象被GC确定为要被回收的垃圾,在回收之前由GC帮你调用这个方法,不是由程序员手动调用。
-
- ② 这个方法与C语言的析构函数不同,C语言的析构函数被调用,那么对象一定被销毁,内存被回收,而
finalize
方法的调用不一定会销毁当前对象,因为可能在finalize()
中出现了让当前对象“复活”的代码。
- ② 这个方法与C语言的析构函数不同,C语言的析构函数被调用,那么对象一定被销毁,内存被回收,而
-
- ③ 每一个对象的
finalize
方法只会被调用一次。
- ③ 每一个对象的
-
- ④ 子类可以选择重写,一般用于彻底释放一些资源对象,而且这些资源对象往往时通过C/C++等代码申请的资源内存。
-
示例:
package top.open1024.objectdemo.demo3;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 13:44
*/
public class Animal {
private String name;
@Override
protected void finalize() throws Throwable {
System.out.println("要挂了,要挂了");
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Animal{" + "name='" + this.name + '\'' + '}';
}
}
package top.open1024.objectdemo.demo3;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 13:44
*/
public class Test {
public static void main(String[] args) {
Animal animal = new Animal();
System.out.println(animal);
animal = null;
System.gc();
}
}
8.5 hashCode()方法
- 方法:
public native int hashCode();
-
解释:
-
- ① 返回当前对象的
hash
值。
- ① 返回当前对象的
-
- ②
hashCode
的常规规定:
- ②
-
-
- 如果两个对象的hash值是不同的,那么这两个对象一定不相等;
-
-
-
- 如果两个对象的hash值是相同的,那么这两个对象不一定相等。
-
-
- ③ 实际开发中,经常重写
hashCod()方法
,尽量让不同的对象产生的hashCode
是不同的。
- ③ 实际开发中,经常重写
-
示例:
package top.open1024.objectdemo.demo4;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 13:44
*/
public class Animal {
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Animal{" + "name='" + this.name + '\'' + '}';
}
}
package top.open1024.objectdemo.demo4;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 14:01
*/
public class Test {
public static void main(String[] args) {
Animal a1 = new Animal();
int hashCode = a1.hashCode();
System.out.println(hashCode); // 356573597
System.out.println("Aa".hashCode()); // 2112
System.out.println("BB".hashCode()); // 2112
}
}
8.6 equals()方法
- 方法:
public boolean equals(Object obj) {
return (this == obj);
}
-
解释:
-
- ① 用于判断当前对象
this
和指定对象obj
是否“相等”
。
- ① 用于判断当前对象
-
- ② 默认情况下,
equals()
方法和==
一样,比较的是对象的地址值。
- ② 默认情况下,
-
- ③ 实际开发中,我们需要进行重写,要求如下:
-
-
- 如果重写
equals()
,那么一定要重写hashCode()
方法,因为规定:
- 如果重写
-
-
-
-
- 如果两个对象调用
equals
返回true,那么要求这两个对象的hashCode
值一定是相等的。
- 如果两个对象调用
-
-
-
-
-
- 如果两个对象的
hashCode
值不同的,那么要求这个两个对象调用equals
方法一定是false。
- 如果两个对象的
-
-
-
-
-
- 如果两个对象的
hashCode
值相同的,那么这个两个对象调用equals
可能是true,也可能是false。
- 如果两个对象的
-
-
-
-
- 如果重写
equals()
,那么一定要遵循如下几个原则:
- 如果重写
-
-
-
-
- 自反性:
x.equals(x)
返回true。
- 自反性:
-
-
-
-
-
- 传递性:
x.equals(y)
为true,y.equals(z)
为true,然后x.equals(z)
也应该为true。
- 传递性:
-
-
-
-
-
- 一致性:只要参与
equals
比较的属性值没有修改,那么无论何时调用结果应该一致。
- 一致性:只要参与
-
-
-
-
-
- 对称性:
x.equals(y)
与y.equals(x)
结果应该一样。
- 对称性:
-
-
-
-
-
非空对象与null
的equals
一定是false。
-
-
-
示例:
package top.open1024.objectdemo.demo5;
import java.util.Objects;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 14:21
*/
public class Person {
private String name;
private int age;
private char gender;
public Person() {}
public Person(String name, int age, char gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
public char getGender() {
return this.gender;
}
public void setGender(char gender) {
this.gender = gender;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || this.getClass() != o.getClass()) {
return false;
}
Person person = (Person)o;
return this.age == person.age && this.gender == person.gender && Objects.equals(this.name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(this.name, this.age, this.gender);
}
@Override
public String toString() {
return "Person{" + "name='" + this.name + '\'' + ", age=" + this.age + ", gender=" + this.gender + '}';
}
}
package top.open1024.objectdemo.demo5;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 14:22
*/
public class Test {
public static void main(String[] args) {
Person p1 = new Person("张三", 25, '男');
Person p2 = new Person("张三", 25, '男');
System.out.println("p1.equals(p2) = " + p1.equals(p2)); // true
}
}
评论区