小小笔记03 面向对象plus
文章目录
1.类变量(静态变量)和类方法(静态方法)
1.1 静态变量 (Static变量)(类变量)
1.1.1 静态变量的定义
-
⭐️静态变量被同一个类的所有对象共享!!
-
任何该类的对象访问的时候,取到的都是相同的值
-
任何该类的对象去修改的时候,修改的也都是同一个变量
-
-
⭐️静态变量在类加载的时候就生成了!!
- 在加载类的时候也初始化了静态变量,也就是说:即使没有创建类对象,只要类加载了,就可以使用静态变量
- 类变量的生命周期是随类的加载开始的,随类的消亡而销毁
-
一些小区别:jdk8以前的版本,static变量在方法区里。jdk8之后认为static变量通常在堆里:当一个类加载的时候,会在堆里生成一个对应这个类的class对象(属于一个原型对象),而静态变量就在这个class对象的尾部。
1.1.2 定义变量
访问修饰符 static 数据类型 变量名
public static int num;
1.1.3 访问变量(前提是满足修饰符的访问权限和范围)
【推荐使用】: 类名.类变量名
或者: 对象名.类变量名
1.1.4 静态变量什么时候使用
需要让某个类的所有对象共享一个变量的时候。
其与实例变量(普通属性)的区别是:类变量是该类所有的对象共享的,而实例变量是每个对象独有的,实例变量不能通过类名.类变量名访问。
注意:在Java中,子类不能继承父类的静态属性。静态属性(也称为类属性)是属于类的,而不是属于任何类的实例。因此,它们不会被子类继承。
但是,子类可以通过类名直接访问父类的静态属性,前提是这些属性是可访问的(例如,它们是公开的或受保护的)。
1.2 静态方法 (Static方法)(类方法)
1.2.1 静态变量的定义
- 当方法使用了static修饰后,该方法就是静态方法。
- 静态方法(类方法)可以通过类名调用,也可以通过对象名调用(需要遵循访问权限);而普通方法只能通过对象名调用。
- 静态方法不允许使用和对象有关的关键字,比如this,super
- 访问问题:
- 【静态方法】 能访问静态方法 或 静态变量, 想要访问非静态的属性时,需要先创建类的对象,再去调用即可。
- 【普通方法】 既能访问普通方法 又能访问静态方法,既能访问普通变量 又能访问静态变量。
1.2.2 定义方法
访问修饰符 static 数据返回类型 方法名(){}
private static double look(){}
1.2.3 访问方法(前提是满足修饰符的访问权限和范围)
【推荐使用】: 类名.类方法名
或者: 对象名.类方法名
1.2.4 静态方法什么时候使用
如果不希望创建实例,也可以调用某个方法:类名 方法名()
。也就是当方法中不涉及任何和对象相关的成员(即当做工具来使用时),就可以把方法设计成静态方法,提高开发效率。
1.3 main方法
public static void main(String[] args){}
1.3.1 main方法说明
- main方法是jvm虚拟机调用的,所以该方法的访问权限必须是
public
- 虚拟机调用main方法的时候,不必创建对象,所以该方法必须是
static
- main方法接受String类型的数组参数args,该数组中—保存着执行java命令时传递给所运行命令的参数。
1.4 代码块
1.4.1 代码块的定义
代码化块又称为初始化块,属于类中的成员(即是类的一部分),类似于方法,将逻辑语句封装在方法体中,通过{ }
包围起来。
但和方法不同,代码块没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是⭐️加载类时或创建对象时隐式调用。
相当于是另外一种形式的构造器(相当于是构造器的补充机制),去做初始化的操作,⭐️代码块调用的顺序优先于构造器。
1.4.2 什么时候用代码块?
如果多个构造器都有重复的语句,可以抽取到初始化块中,提高代码复用性。
1.4.3 代码块的基本语法
[修饰符可有可无]{
代码
};
- 修饰符可选,只能选择:不写 / static
- static修饰的叫做静态代码块
- 逻辑语句可以为任意语句
- 最后的分号可以写也可以省略
1.4.4 细节说明
-
当多个构造器都有相同的语句 ,这样代码看起来比较冗余 , 这时我们可以把相同的语句,放入到一个代码块中。这样当我们不管调用哪个构造器,创建对象,都会先调用代码块的内容 。代码块调用的顺序优先于构造器。
-
💁静态代码块 & 静态属性初始化:随着类的加载而执行,并且只会执行一次。
💁普通代码块 & 普通属性初始化:每创建一个该类的对象,就执行一次。
-
⭐️⭐️⭐️类什么时候被加载❓【重要!!!!】
-
创建对象实例的时候
new
; -
创建子类对象实例父类也会被加载 ,且先加载父类后加载子类;
-
使用类的静态成员(静态变量,静态方法)时,该类也会被加载。
-
-
普通的代码块,在创建对象实例的时候,会被隐式的调用,对象被创建一次,就会调用一次;
如果只是使用类的静态成员,普通代码块并不会执行(因为使用类的静态成员并没有创建对象) -
💥构造器的最前面其实是隐藏了super()和 调用本类的普通代码块以及普通属性初始化。
class A{} public A(){//构造器 System.out.println("okok"); } }
上面的构造器在实际运行时,实际上隐藏的一些执行要求是这样的⤵️
class A{} public A(){//构造器 super(); // 1.默认调用父类的无参构造器 // 2.调用普通代码块 以及 普通属性初始化 // 3.最后再调用构造器内部的语句 System.out.println("okok"); } }
举例说明
执行过程:新建一个BBB类的对象,首先进入BBB构造器,进入后:
(1)默认的super( )调用父类AAA的无参构造器
(2)默认的super( )调用父类Obgect的无参构造器 返回
(3)调用本类AAA的代码块,输出【这是AAA的普通代码块】
(4)执行AAA的构造器中的内容,输出【这是AAA的构造器】
(5)调用本类BBB的代码块,输出【这是BBB的普通代码块】
(6)执行BBB的构造器中的内容,输出【这是BBB的构造器】 -
⭐️⭐️⭐️创建一个对象时:在 一个类中 的调用顺序是:
-
先调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)
-
再调用普通代码块和普通属性的初始化(注意: 普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)
-
最后调用构造器方法
-
-
⭐️⭐️⭐️创建一个子类对象时:静态代码块、静态属性初始化、普通代码块、普通属性初始化、构造方法的调用顺序是:
-
创建对象的时候首先就是要进行类的加载(先加载父类,后加载子类)
- 父类的静态代码块 和 静态属性初始化(优先级一样,按定义顺序执行)
- 子类的静态代码块 和 静态属性初始化(优先级一样,按定义顺序执行)
-
进行对象的创建(先从子类的构造器开始,子类构造器里默认包含super() 调用父类无参构造器 and 调用本类的普通代码块以及普通属性初始化,之后在进行构造器内容)
- 父类的普通代码块 和 普通属性初始化(优先级一样,按定义顺序执行)
- 父类的构造器方法
- 子类的普通代码块 和 普通属性初始化(优先级一样,按定义顺序执行)
- 子类的构造器方法
-
package com.hspedu.static_;
public class constructor {
public static void main(String[] args) {
new BBB();
// 先加载类 加载父类-加载子类
// 在执行创建对象的一些操作:子类构造器--super()父类构造器--普通代码块、普通属性初始化--构造器内容
}
}
class AAA{ // 父类
public int n = n();
public static int m = m();
static {
System.out.println("这是AAA的静态代码块");
}
{
System.out.println("这是AAA的普通代码块");
}
public AAA(){
// (1) super() 调用父类Object的构造器
// (2) 调用本类的普通代码块以及普通属性初始化
System.out.println("这是AAA的构造器");
}
public int n(){
System.out.println("这是AAA的普通属性初始化");
return 1;
}
public static int m(){
System.out.println("这是AAA的静态属性初始化");
return 0;
}
}
class BBB extends AAA{ //子类
static {
System.out.println("这是BBB的静态代码块");
}
{
System.out.println("这是BBB的普通代码块");
}
public BBB(){
// (1) super() 调用父类AAA的构造器
// (2) 调用本类的普通代码块以及普通属性初始化
System.out.println("这是BBB的构造器");
}
}
>>>输出
这是AAA的静态属性初始化
这是AAA的静态代码块
这是BBB的静态代码块
这是AAA的普通属性初始化
这是AAA的普通代码块
这是AAA的构造器
这是BBB的普通代码块
这是BBB的构造器
-
静态代码块只能调用静态成员(静态属性和静态方法)
普通代码块可以调用任意成员 -
静态属性和方法的继承
- 静态方法不能继承
- 如果父类中有静态方法,子类中没有,但是子类可以调用父类的静态方法
- 如果父类中有静态方法,子类中也有跟父类相同的静态方法,那么父类不管是 类名调用 还是 实例调用 都是调用的父类的静态方法
- 子类不管是 类名调用 还是 实例调用 都是调用子类的静态方法
1.4.5 例题
class Person {
public static int total;//静态变量
static {//静态代码块
total = 100;
System.out.println("in static block!");//(1) 只在加载类的时候执行一次!!一次!!
}
}
public class Test {
public static void main(String[] args) {
System.out.println("total = "+ Person.total); //100
System.out.println("total = "+ Person.total); //100
}
}
>>>输出
in static block!
100
100
⭐️1.5 单例设计模式
1.5.1 什么是单例模式
单例——单个的实例
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
单例模式有两种方式: 1)饿汉式 2)懒汉式
1.5.2 实现步骤
演示饿汉式和懒汉式单例模式的实现步骤如下:
- 构造器私有化 -> 防止直接 new
- 在类的内部创建对象
- 向外暴露一个静态的公共方法
1.⭐️饿汉式单例模式【只要类被加载,对象就被创建了】
0391_韩顺平Java_单例模式饿汉式_哔哩哔哩_bilibili
package com.hspedu.static_.single;
public class SingleTon01 {
public static void main(String[] args) {
使用类名就可以调用getInstance静态public方法,返回创建的gf对象,
且由于创建对象时 gf是静态的,所以类加载的时候只执行一次,因此无论后面怎么调用,返回的gf对象都是同一个
GirlFriend instance1 = GirlFriend.getInstance();
System.out.println(instance1);
GirlFriend instance2 = GirlFriend.getInstance();
System.out.println(instance2);
// instance1 和 instance2是一个对象
System.out.println(instance1 == instance2);
}
}
// 有一个类 GirlFriend 只允许有一个女朋友!!!!
// 如何保证只能创建一个GirlFriend对象?【饿汉式】
// 1.构造器私有化 —— 不能new对象
// 2.在类的内部创建对象(该对象是static)
// 3.提供一个public的static方法,返回gf对象
class GirlFriend{
private String name;
// 【在类的内部创建一个对象】
// 同时,为了保证静态方法getInstance可以使用 gf对象,所以需要修饰新建的gf对象为static
private static GirlFriend gf = new GirlFriend("小红");
// 【构造器私有化】 保证不能在外面肆意new对象
private GirlFriend(String name) {
this.name = name;
}
// 【提供一个public的static方法,返回gf对象】
// static用来保证 不用新建一个GirlFriend对象就可以调用这个方法
public static GirlFriend getInstance(){
return gf;
}
@Override
public String toString() {
return "GirlFriend{" +
"name='" + name + '\'' +
'}';
}
}
>>> GirlFriend{name='小红'}
GirlFriend{name='小红'}
true
2.⭐️懒汉式单例模式
0392_韩顺平Java_单例模式懒汉式_哔哩哔哩_bilibili
package com.hspedu.static_.single;
public class SingleTon02 {
public static void main(String[] args) {
Cat mycat1 = Cat.getInstance();
System.out.println(mycat1);
Cat mycat2 = Cat.getInstance();
System.out.println(mycat2);
// mycat1 和 mycat2 是一个对象
System.out.println(mycat1 == mycat2);
}
}
// 希望程序进行中, 只能创建一个Cat对象!!!!!
// 使用单例模式【懒汉式】
// 1.仍然构造器私有化
// 2.定义一个static属性对象,但是不初始化
// 3.提供一个public的static方法 可以返回一个Cat对象
// 4.懒汉式:只有当用户使用getInstance方法的时候,才返回Cat对象,后面再次调用时会返回上次创建的cat对象,从而保证了单例
class Cat{
private String name;
// 【定义一个static属性对象 但是不初始化,没有new创建一个对象的话构造器是不会被调用的】
private static Cat cat;
// 【构造器私有化】
private Cat(String name){
this.name = name;
}
// 【提供一个public的static方法 可以返回一个Cat对象】
// 只有当用户使用getInstance方法的时候,才返回Cat对象。如果不是调用getInstance方法,那么虽然可能类被加载,但是没有创建对象
// 后面再次调用时会返回上次创建的cat对象,从而保证了单例模式
public static Cat getInstance(){
if(cat == null){ // 如果没有创建cat对象,那么就创建一个cat对象
cat = new Cat("小花猫");
}
return cat;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}
>>>Cat{name='小花猫'}
Cat{name='小花猫'}
true
3.饿汉式 VS 懒汉式创建单例模式
Runtime是饿汉式的:在类加载就创建对象实例,可能存在资源浪费的问题
1.6 final关键字(不希望类被继承、方法被重写、属性被修改)
1.6.1 final用法
final 可以修饰类、属性、方法和局部变量在某些情况下,程序员可能有以下需求,就会使用到final:
-
当不希望类被继承时,可以用final修饰.【final class】
-
当不希望父类的某个方法被子类覆盖/重写(override)时,可以用final关键字修饰。【访问修饰符 final 返回类型 方法名】
-
当不希望类的的某个属性的值被修改,可以用final修饰。【public final double RATE=0.08】
-
当不希望某个局部变量被修改,可以使用final修饰【final double RATE=0.08】
1.6.2 final细节
-
final修饰的属性又叫常量,一般用XX_XX_XX来命名(大写)
-
final修饰的属性在定义时必须赋初值,并且以后不能再修改!
赋值可以在如下位置之一 [ 选择一个位置赋初值即可 ]-
在定义时: 如 public final double TAX_RATE=0.08
-
在构造器中赋值
-
在代码块中赋值
private final double PI = 3.14; // 赋值位置1 :定义时候就赋值 private final double PI; // 赋值位置2 : 构造器内 public Circle(double radius){ this.radius = radius; PI = 3.14; } private final double PI; //赋值位置3:代码块中赋值 { PI = 3.14; }
-
-
如果final修饰的属性是静态的,则初始化的位置只能是:
- 在定义时
- 在静态代码块
- 不能在构造器中赋值❌【静态属性的初始化时需要类加载,类加载的时候构造器没被调用,创建对象的时候才调用构造器】
-
final类不能继承,但是可以实例化对象。
-
如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承。
-
一般来说如果已经是一个final类了,就没必要修饰final方法了[都不能被继承了 那重写不重写方法没意义]
-
final不能修饰构造器。
-
final和static搭配使用,不会导致类的加载,效率更高。
-
包装类(Integer Double Float Boolean String等)都是final类,都不能被继承
小题目
final x,++改变了x的值所以不行,x+1没有改变本身x的值所以可以
1.7 抽象类
当父类的某些方法需要声明,但是又不确定如何实现的时候
=》可以将其声明为抽象方法——用abstract关键字来修饰该方法,对应的这个类就是抽象类——用abstract修饰该类。
package com.hspedu.abstract_;
public class Abstract01 {
public static void main(String[] args) {
}
}
abstract class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
//思考:这里 eat 这里你实现了,其实没有什么意义
//即: 父类方法不确定性的问题
//===> 【考虑将该方法设计为抽象(abstract)方法】
//===> 所谓抽象方法就是没有实现的方法
//===> 所谓没有实现就是指,没有方法体
//===> 【当一个类中存在抽象方法时,需要将该类声明为 abstract 类】
//===> 一般来说,抽象类会被继承,由其子类来实现抽象方法.
public abstract void eat() ;
}
class cat extends Animal{
public void eat(){
//如果一个类继承了抽象类,则他必须实现抽象类的所有抽象方法,否则这个类也必须声明为抽象类
}
}
- ❌抽象类不能被实例化(就是不能new)
- 抽象类不一定要包含抽象方法
- 一旦包含了abstract方法,那么这个类就必须是abstract类
- abstract只能修饰类和方法,不能修饰属性
- 抽象类还是类,抽象类里面想有啥都可以,可以有构造器、非抽象方法、静态属性巴拉巴拉的任意成员
- ❌抽象方法没有主体 (即没有{ } ← 这部分内容)
- 如果一个类继承了抽象类,则他必须实现抽象类的所有抽象方法,否则这个类也必须声明为抽象类
- ❌抽象方法不能使用private final static来修饰,因为这些关键字都是和重写相违背的
- private的方法不能被重写
- final关键字修饰方法:表示方法不可以被重写
- **static关键字和方法重写无关!!**static方法是属于类而不是对象的方法,可以直接通过类名调用,而不需要创建对象实例。抽象方法需要被子类实现,而静态方法属于类而不属于对象,因此把抽象方法声明为静态方法没有意义
1.8 模板设计模式(抽象类的最佳实践)
需求:有多个类完成不同的任务job,要求统计得到各自完成任务的时间
模板设计模式能解决的问题:
-
当功能内部一部分实现是确定,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
-
编写一个抽象父类,父类提供了多个子类的通用方法,并把一个或多个方法留给其子类实现,就是一种模板模式
设计一个抽象类(Template),能完成如下功能:
- 编写方法
calculateTime()
,可以计算某段代码的耗时时间 - 编写抽象方法
job
- 编写一个子类
Sub
,继承抽象类Template
,并实现job方法
- 编写一个测试类
TestTemplate
,看看是否好用
package com.hspedu.static_.abstract_.TestTemplate;
public abstract class Template { // 抽象类-模板设计模式
public abstract void job(); // 抽象方法
public void calculateTime(){ // 实现方法 调用了job方法
long start = System.currentTimeMillis();
job(); // 动态绑定机制,调用对象方法的时候,看运行类型,和对象绑定。
// sub1.calculateTime(),对象sub1的运行类型是Sub1,调用calculateTime方法,子类Sub1没有,找到父类Template类调用calculateTime方法,运行到调用job方法时,根据动态绑定机制:调用的是sub1运行类型——即Sub1的job方法。
// sub2.calculateTime()就调用Sub2的job
long end = System.currentTimeMillis();
System.out.println("耗时间" + (end-start));
}
}
class Sub1 extends Template{
@Override
public void job() { // 实现Template类的抽象方法job
int num = 0;
for (int i = 0; i < 80000; i++) {
num += i;
}
}
}
class Sub2 extends Template{
@Override
public void job() { // 实现Template类的抽象方法job
int num = 0;
for (int i = 0; i < 8000000; i++) {
num += i;
}
}
}
package com.hspedu.static_.abstract_.TestTemplate;
public class Test {
public static void main(String[] args) {
Sub1 sub1 = new Sub1(); // 新建一个子类对象 编译类型和运行类型都是Sub1
sub1.calculateTime(); // 调用模板类的方法计算时间
Sub2 sub2 = new Sub2(); // 新建一个子类对象 编译类型和运行类型都是Sub2
sub2.calculateTime(); // 调用模板类的方法计算时间
}
}
⭐️动态绑定机制:当调用对象方法的时候,该方法会和该对象的内存地址(也就是运行类型)绑定
1.9 接口
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据具体情况把这些方法写出来
1.9.1 接口语法
interface 接口名{
//属性
//可以有3种方法(1.抽象方法(不需要abstract关键字) 2.默认实现方法(default) 3.静态方法(static))
}
class 类名 implements 接口{
自己属性;
自己方法;
必须实现的使用的接口的抽象方法;
}
- 在接口中,抽象方法可以省略abstract关键字
- JDK 7.0之前接口里所有的方法都没有方法体,即都是抽象方法
- JDK 8.0之后接口里可以有默认实现方法,需要使用default关键字修饰。也可以有静态方法static。
1.9.2 接口使用场景
// 定义接口
public interface Manager {
void connect(); // 接口中所有的方法必须是public方法(默认),以及默认是abstract的;
void close();
}
// A程序员实现接口:把接口中的方法完成
public class MySQLDB implements Manager{
@Override
public void connect() {
System.out.println("connect MYSQL");
}
@Override
public void close() {
System.out.println("close MYSQL");
}
}
// B程序员实现接口:把接口中的方法完成...
// 测试
public class test {
public static void main(String[] args) {
// 连接MYSQL
MySQLDB mySQLDB = new MySQLDB();
my.mytest(mySQLDB);
}
}
class my{
public static void mytest(Manager manager){ // 接入接口,通过接口来调用方法
manager.connect();
manager.close();
}
}
>>>输出:
connect MYSQL
close MYSQL
1.9.3 接口的注意事项
-
接口不能被实例化(因为接口本身是一个抽象的概念,希望的就是其他类实现它);
-
接口中所有的方法必须是public方法;
-
接口中抽象方法可以不用abstract修饰;
-
一个普通类实现(implements)接口,就必须把该接口的所有方法都实现。
public interface myinterface { void look(); void eat(); } // 普通类实现接口,就必须把该接口的所有方法都实现 class Tiger implements myinterface{ public void look() { System.out.println("looking"); } public void eat() { System.out.println("eating"); } }
-
如果用抽象类实现接口,那么就可以不用实现接口方法
public interface myinterface { void look(); void eat(); } // 如果用抽象类实现接口,那么就可以不用实现接口方法 abstract class Tiger implements myinterface{ }
-
一个类可以同时实现多个接口~~
interface myinterface1 { // 接口1 void look(); } interface myinterface2 { // 接口2 void look(); } // 一个类可以同时实现多个接口 class Tiger implements myinterface1,myinterface2{ public void look() { System.out.println("looking"); } public void eat() { System.out.println("eating"); } }
-
接口中的属性,只能是final的!且只能是
public static final
修饰符!!!!!!!!!int a = 1;
相当于:public static final int a = 1;
-
接口中属性的访问形式:
接口名.属性名
-
接口不能继承其他类,但是接口可以继承(extends)多个别的接口
接口和接口是:
interface A extends B,C;
类和接口是:
class A implement B;
-
接口的修饰符只能是
public
或者默认
,和类的修饰符一样public interface myinterface{}
interface myinterface{}
1.9.4 接口练习题
都正确 都输出23。因为A里面的int a = 23 实际上等价于public static final int a = 23;
因为a变量是public的,所以b是对象实例访问a可以。
因为接口A中a变量为static ,所以可以用过类名A调用
在Java中,子类不能继承父类的静态属性。静态属性(也称为类属性)是属于类的,而不是属于任何类的实例。因此,它们不会被子类继承。但是,子类可以通过类名直接访问父类的静态属性,前提是这些属性是可访问的public。而B实现了A,因此B可以使用A里面的属性。
1.9.5 接口和继承类
实现接口是对 Java 单继承机制的补充
- 当子类继承了父类,就自动的拥有父类的功能。
- 如果子类需要扩展功能,可以通过实现接口的方式进行扩展
接口比继承更加灵活,继承满足is-a的关系,接口组织需要满足like-a的关系
接口在一定程度上实现代码的解耦【即接口规范性 + 动态绑定机制】
public class ExtendsVsInterface {
public static void main(String[] args) {
LittleMonkey wuKong = new LittleMonkey("悟空");
wuKong.climbing();
wuKong.swimming();
wuKong.flying();
}
}
//猴子
class Monkey {
private String name;
public Monkey(String name) {
this.name = name;
}
public void climbing() {
System.out.println(name + " 会爬树...");
}
public String getName() {
return name;
}
}
//接口
interface Fishable {
void swimming();
}
interface Birdable {
void flying();
}
class LittleMonkey extends Monkey implements Fishable,Birdable {
public LittleMonkey(String name) {
super(name);
}
@Override
public void swimming() {
System.out.println(getName() + " 通过学习,可以像鱼儿一样游泳...");
}
@Override
public void flying() {
System.out.println(getName() + " 通过学习,可以像鸟儿一样飞翔...");
}
}
// 小结: 当子类继承了父类,就自动的拥有父类的功能
// 如果子类需要扩展功能,可以通过实现接口的方式扩展.
// 可以理解 实现接口 是 对 java 单继承机制的一种补充
1.9.6 接口的多态
1.多态参数 + 接口的多态和继承的多态对比
packagecom.hspedu.interface_;
public class InterfacePolyParameter{
public static void main(String[] args){
//接口的多态体现
//接口类型的变量if01 可以指向 实现了IF接口类的对象实例
IF if01 = new Monster();
if01 = newCar();
//继承体现的多态
//父类类型的变量a 可以指向 继承AAA的子类的对象实例
AAA a = new BBB();
a = new CCC();
}
}
// 接口
interface IF{}
class Monster implements IF{} // 实现接口
class Car implements IF{} // 实现接口
// 类
class AAA{}
class BBB extends AAA{} // 继承父类
class CCC extends AAA{} // 继承父类
2.多态数组
package com.hspedu.interface_.test3;
public class InterfacePolyArr {
public static void main(String[] args) {
// 多态数组: 定义一个接口类型数组
Usb[] usbs = new Usb[2];
usbs[0] = new Phone(); //Phone实现了接口 所以可以放到数组内
usbs[1] = new Carema();
/*
给Usb数组中,存放Phone和Carema对象,
Phone类还有一个特有的方法call()
请遍历Usb数组,如果是Phone对象,除了调用Usb接口定义的方法外,还需要调用Phone 特有方法call
*/
for (int i = 0; i < usbs.length; i++) { // 遍历Usb数组,调用work方法,调用Phone的特有方法
usbs[i].work(); //动态绑定
if(usbs[i] instanceof Phone){
((Phone) usbs[i]).call(); //向下转型
}
}
}
}
interface Usb{
void work();
}
class Phone implements Usb{ // Phone实现Usb接口
@Override
public void work() {
System.out.println("手机工作中");
}
public void call(){ //Phone类的特有方法
System.out.println("手机可以打电话");
}
}
class Carema implements Usb{// Carema实现Usb接口
@Override
public void work() {
System.out.println("相机工作中");
}
}
3.接口存在多态传递现象
如果IG继承了IH接口,而Teacher类实现了IG接口 ---------> 那么,实际上就相当于Teacher类也实现了IH接口.
/**
*演示多态传递现象
*/
public class InterfacePolyPass{
public static void main(String[] args){
//☆接口类型的变量可以指向实现了该接口的类的对象实例(多态 向上转型类比)
IG ig = new Teacher();
//如果IG继承了IH接口,而Teacher类实现了IG接口
//那么,实际上就相当于Teacher类也实现了IH接口.
//这就是所谓的接口多态传递现象.
IH ih = new Teacher();
}
}
interface IH{
void hi();
}
interface IG extends IH{}
class Teacher implements IG{
// 这里要实现hi方法的原因就是,如果IG继承了IH接口,Teacher又实现了IG,那么就相当于Teacher也要实现IH,所以必须这样,不然会报错。
@Override
public void hi(){}
}
1.9.7 接口的练习题
//☆访问接口的 x 就使用 A.x
//☆访问父类的 x 就使用 super.x
interface A{
int x=0;
} //想到等价public static final int x=0;
class B{
int x=1;
}//普通属性
class C extends B implements A{
public void pX(){
//System.out.println(x);//错误,原因不明确x
//可以明确的指定x
//☆访问接口的 x 就使用 A.x
//☆访问父类的 x 就使用 super.x
System.out.println(A.x + " " + super.x);
}
public static void main(String[] args) {
new C().pX();
}
}