Java基础部分总结—03
- 接口
- 多态
- final关键字
- 内部类
1 接口—interface
定义格式
public interface 接口名称 {
// 抽象方法 abstract 不实现,没有ff体
// 默认方法 default 实现, 有方法体
// 静态方法 static 实现, 有方法体
// 私有(静态)方法 private/private static
//成员函数 public static final(表示不可改变) 可省略,但可是不可改变,且定义的时候必须进行赋值,
//【类似与C++中的const】
}
-
抽象方法:使用 abstract 关键字修饰,可以省略,没有方法体。该方法供子类实现使用。
-
默认方法:使用 default 修饰,不可省略,供子类调用或者子类重写。
-
静态方法:使用 static 修饰,供接口直接调用。
-
私有方法和私有静态方法:使用 private 修饰,供接口中的默认方法或者静态方法(只能调用私有静态方法)调用。
实现—implements
非抽象子类实现接口:
1. 必须重写接口中所有抽象方法。
2. 继承了接口的默认方法,即可以直接调用,也可以重写。
class 类名 implements 接口名 {
// 重写接口中抽象方法【必须】
// 重写接口中默认方法【可选】
}
-
抽象方法的使用
和抽象类中的抽象方法一致,都必须进行@override重写,否则该类为抽象类。
如果抽象方法有重名的,只需重写一次。 -
默认方法的使用
由于默认方法在接口中方法体,故可以继承,可以进行重写,但是只能通过实现类的对象来调用。
接口中,有多个默认方法时,实现类都可以继承使用。如果默认方法有重名的,必须重写一次。 -
静态方法的使用
静态方法只能使用接口名来调用,不可以通过实现类的类名或实现类的对象调用。
接口中,存在同名的静态方法并不会冲突,原因是只能通过各自接口名访问静态方法。
不能通过接口的实现类来调用接口中的静态方法,因为接口可以多继承,会带来重名的情况。 -
私有方法和私有静态方法
私有(静态)方法只能为接口中的默认方法和静态方法调用,即只能私自使用,外部实现类不能用。
存在的原因:如果一个接口中有多个默认方法,并且方法中有重复的内容,那么可以抽取出来,封装到私有方法中,供默认方法去调用。从设计的角度讲,私有的方法是对默认方法和静态方法的辅助。- 私有方法
只有默认方法可以调用。 - 私有静态方法
默认方法和静态方法可以调用。
换句话说,静态方法里面只能是私有静态方法,默认方法里可以有私有方法和私有静态方法。
- 私有方法
-
成员常量
接口中,无法定义成员变量,但是可以定义常量,其值不可以改变(在定义时就要进行赋值),默认使用public static final修饰。
接口和继承同时存在时的优先级
当一个类,既继承一个父类,又实现若干个接口时,父类中的成员方法与接口中的默认方法重名,子类就近选择执
行父类的成员方法。
接口的继承
一个接口能继承另一个或者多个接口,这和类之间的继承比较相似。接口的继承使用 extends 关键字,子接口继
承父接口的方法。如果父接口中的默认方法有重名的,那么子接口需要重写一次。
- 一些重名的区分:
- 1)静态方法的重名:静态方法有方法体,在调用时直接用接口名.静态方法,所以在接口的多继承和实现出现重名情况时,并不需要重写。
- 2)抽象方法的重名:抽象方法没有方法体,在实现的时候必须重写,否则其为抽象类,在接口的实现类中出现与其他抽象方法重名的情况下,只需重写一次即可。
- 3)默认方法的重名:默认方法有方法体,在实现类或多继承时出现重名的情况必须对默认方法进行重写。
小贴士:
子接口重写默认方法时,default关键字可以保留。
接口实现类重写默认方法时,default关键字不可以保留。
代码演示
- 接口A
public interface A {
public static final int a = 10;//成员常量
//public static int b ;//成员常量定义时必须初始化
int c=10;//不写static系统会默认加上public static final
public abstract void methodA();//抽象方法
public default void methodB() {//默认方法
System.out.println("A::methodB");
this.methodD();//默认方法调用私有方法
methodE();//默认方法调用私有静态方法(注意!静态的没有this指针)
}
public default void methodF() {//默认方法
System.out.println("A::methodF");
this.methodD();//默认方法调用私有方法
methodE();//默认方法调用私有静态方法(注意!静态的没有this指针)
}
public static void methodC() {//静态方法
System.out.println("A::methodC");
//methodD();//Err静态方法不能调用非静态的成员变量
methodE();//静态方法调用私有静态方法(注意!静态的没有this指针)
}
//私有方法
private void methodD() {
System.out.println("我是私有方法");
}
//私有静态方法
private static void methodE() {
System.out.println("我是私有静态方法");
}
}
- 接口B
/*
* 接口B,抽象、默认、静态方法与接口A重名
* */
public interface B {
public abstract void methodA();//抽象方法-->与接口A的抽象方法重名
public default void methodB() {//默认方法--->与接口A的默认方法重名
System.out.println("B::methodB");
}
public static void methodC() {//静态方法--->与接口A的静态方法重名
System.out.println("B::methodC");
}
}
- 类D的子类C
/*
* 用于继承的类C,其为类D的子类
* */
public class C {
public void methodF() {//默认方法//抽象方法--->与接口A中的抽象方法重名
{
System.out.println("我是子类C中的与接口A的抽象方法重名的成员方法methodF()");
}
}
}
- 接口的实现类
/*
* 接口实现类,实现了接口A、B
* */
public class D extends C implements A, B {
@Override
public void methodA() {//接口的实现类中出现抽象方法重名只需重写一个抽象方法即可
System.out.println("我是实现类中的抽象方法");
}
@Override
public /*default*/ void methodB() {//接口实现类中的默认方法出现重名时,一定要重写,且不能加default,default只能出现在接口中
System.out.println("我是实现类中重写的默认方法");
}
//接口实现类中重复的默认方法不用重写
}
- 主函数
public class testMain {
public static void main(String[] args) {
D d = new D();
d.methodA();// 调用实现类重写的抽象方法 out :我是实现类中的抽象方法
d.methodB();// 调用实现类重写的默认方法 out: 我是实现类中重写的默认方法
A.methodC();// 直接接口名.静态方法调用接口A中的静态方法 out: A::methodC 我是私有静态方法
int re = A.a;// 接口名.静态常量调用接口A中的静态常量
System.out.println("re=" + re);// out: re=10
d.methodF();// 调用继承和接口A同名的方法 out: 我是子类C中的与接口A的抽象方法重名的成员方法methodF()
}
}
多态
同一行为,通过不同的事物,可以体现出来的不同形态。
实现条件【前提】
- 1)继承或实现(接口)【二选一】。
- 2)方法的重写【意义体现:不重写,无意义】
- 3)父类指针指向之类对象【格式体现】
可在创建对象或传参时实现。
注意:
多态指的是成员方法,对于成员变量不能被重写,故通过对象直接访问成员变量时不能发生多态现象,但是可以通过成员方法访问,且方法被重写时可以方法多态。
- 父类
/*
父类
* */
public abstract class Fu {
private String name;
private int id;
public int socre = 10;
public Fu() {
}
public Fu(String name, int id) {
this.name = name;
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public abstract void funcA();//抽象方法
public void funcB() {
System.out.println("Fu::funcB");
}
}
- 子类
public class Zi extends Fu {
public int socre = 100;
@Override
public void funcA() {
System.out.println("子类抽象方法的重写");
}
@Override
public void funcB() {
super.funcB();// 调用父类的成员方法
System.out.println("覆盖重写了父类的方法");
}
public void funcC() {
System.out.println("子类特有的成员方法");
}
}
- Main函数
public class MainTest {
public static void main(String[] args) {
/* 父类指针指向子类对象[多态]
* 此时会自动方法向上转型:Zi---->Fu
* 成员方法:编译看左,运行看右 [编译的时候,只有左边的Fu类中有相关方法,编译才通过,因为只有父类中有才可以点出来其方法]
* 成员变量:编译运行都看左,因为成员变量不会方法多态现象,谁.的就看谁的成员变量
* */
Fu fu = new Zi();
fu.funcA();// 子类抽象方法的重写
fu.funcB();// Fu::funcB 覆盖重写了父类的方法
System.out.println("socre=" + fu.socre);// socre=10
// 为了调用子类特有的成员方法,强制向下转型,转型前需判断
if (fu instanceof Zi) {
Zi zi = (Zi) fu;
zi.funcC();// 子类特有的成员方法
}
System.out.println("=============================");
test(new Zi());// 子类抽象方法的重写 Fu::funcB 覆盖重写了父类的方法 socre=10
}
//在传参时发生父类引用指向子类对象发生多态
private static void test(Fu fu) {
fu.funcA();
fu.funcB();
System.out.println("socre=" + fu.socre);
}
}
final关键字
学习了继承后,我们知道,子类可以在父类的基础上改写父类内容,比如,方法重写。那么我们能不能随意的继承API中提供的类,改写其内容呢?显然这是不合适的。为了避免这种随意改写的情况,Java提供了 final 关键字,用于修饰不可改变内容。
- final: 不可改变。可以用于修饰类、方法和变量。
- 类:被修饰的类,不能被继承。
- 方法:被修饰的方法,不能被重写。
- 变量:被修饰的变量,不能被重新赋值。变量可以是基本类型也可以是引用类型
【相当于c++或c#中的const】
注意:
- 被final修饰的常量名称,一般都有书写规范,所有字母都大写。
- final修饰成员变量的时候,由于成员变量有默认值问题,故必须手动给起赋值–>显示初始化或构造方法初始化,当使用构造方法赋值的时候,必须保证所有的构造方法都对final的成员变量赋值。
修饰类
API中的String类:public final class String ,由于其被final修饰,所以使得数组的长度不能发生改变。
格式:
final class 类名 {
}
修饰方法
格式:
修饰符 final 返回值类型 方法名(参数列表){
//方法体
}
重写被 final 修饰的方法,编译时就会报错
修饰变量
- 1)局部变量—基本类型
基本类型的局部变量,被final修饰后,只能赋值一次,不能再更改。
局部变量没有默认值,成员变量有默认值
注意和接口中的常量区分,接口中的常量需要定义时就赋值,而这里可以定义与赋值分开
public class FinalDemo1 {
public static void main(String[] args) {
// 声明变量,使用final修饰
final int a;
// 第一次赋值
a = 10;
// 第二次赋值
a = 20; // 报错,不可重新赋值
// 声明变量,直接赋值,使用final修饰
final int b = 10;
// 第二次赋值
b = 20; // 报错,不可重新赋值
}
}
- 2)局部变量—引用类型
引用类型的局部变量,被final修饰后,只能指向一个对象,地址不能再更改。**但是不影响对象内部的成员变量值的修改,**代码如下:
public class FinalDemo2 {
public static void main(String[] args) {
// 创建 User 对象
final User u = new User();
// 创建 另一个 User对象
u = new User(); // 报错,指向了新的对象,地址值改变。
// 调用setName方法
u.setName("张三"); // 可以修改
}
}
-
图解:
【注】final修饰的是对象,即创建的对象的地址不变但是创建的对象内部的成员没有被final修饰,故可以改变,像c/c++中的二级指针的概念,这里final修饰的是一级指针。 -
3)成员变量
-
final修饰成员变量的时候,由于成员变量有默认值问题,故必须在创建对象的同时手动给其赋值–>显示初始化或构造方法初始化,当使用构造方法赋值的时候,必须保证所有的构造方法都对final的成员变量赋值
-
成员变量的初始化分为显示初始化和构造函数初始化两种。
-
显示初始化:【在定义时直接赋初值】
public class User {
final String USERNAME = "张三";
private int age;
}
- 构造方法初始化。
【当使用构造方法赋值的时候,必须保证所有的构造方法都对final的成员变量赋。】
public class User {
final String USERNAME ;
private int age;
public User(String username, int age) {
this.USERNAME = username;
this.age = age;
}
}
内部类
将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类。
定义在类中方法外的类。【内部类相当与外部类的一个成员】
在描述事物时,若一个事物内部还包含其他事物,就可以使用内部类这种结构。
定义格式:
class 外部类 {
class 内部类{
}
}
注意:
- 内部类的this指针指的是内部类本身
访问特点
- 内部类可以直接访问外部类的成员,包括私有成员。【外部类中的成员对于本类都是可以访问的】
- 外部类要访问内部类的成员,必须要建立内部类的对象。【有内部类的实体对象才有内部类的成员】,外部类可以直接访问内部类的私有成员。【不需要通过Get()Set()方法】
创建内部类对象格式:
// 有了外部类才有内部类,故先要new外部类,在创建内部类对象
外部类名.内部类名. 对象名 = new 外部类型().new 内部类型();
代码实现
/*
创建并调用匿名内部类
* */
public class InnerClass {
private int Id = 10;
private String name = "张三";
public void methodA() {
System.out.println("InnerClass::methodA");
}
public void methodC() {
// 外部类要访问内部类的成员,必须要建立内部类的对象
// 外部类可以直接访问内部类的私有成员,不需要通过Get\Set方法
InnerClass2 innerClass2 = new InnerClass2();
innerClass2.Id2 = 111;
innerClass2.name2 = "李四";
System.out.println("name:" + innerClass2.name2 + "id:" + innerClass2.Id2);
}
public class InnerClass2 {
// Id、name和InnerClass2处于平等的位置,都是InnerClass的成员,也就是Id、name和Id2、name2处于平等平等的地位
// 唯一的区别是Id2、name2的访问要先创建InnerClass2的实例,所以外部类可以创建对象后直接访问其成员,包括私有成员
private int Id2;
private String name2;
public void methodB() {
System.out.println("name:" + name + "id:" + Id);
}
}
}
public class MainTest {
public static void main(String[] args) {
InnerClass innerClass = new InnerClass();
innerClass.methodA();
innerClass.methodC();
// 创建内部类对象
InnerClass.InnerClass2 innerClass2 = new InnerClass().new InnerClass2();
innerClass2.methodB();
}
}
是内部类的简化写法。它的本质是一个带具体实现的父类或者父接口的匿名的子类对象。开发中,最常用到的内部类就是匿名内部类了。以接口举例,当你使用一个接口时,似乎得做如下几步操作,
- 1.定义子类
- 2.重写接口中的方法
- 3…创建子类对象
- 4.调用重写后的方法
我们的目的,最终只是为了调用方法,那么能不能简化一下,把以上四步合成一步呢?匿名内部类就是做这样的快
捷方式。
代码实现
- 定义接口:
/*
* 定义接口:
* */
public interface Innerable {
abstract void func();
}
- 创建并调用匿名内部类
/*
创建并调用匿名内部类
* */
public class InnerClass {
public static void main(String[] args) {
// 1.等号右边:定义并创建该接口的子类对象
// 2.等号左边:是多态,接口类型引用指向子类对象
Innerable innerable = new Innerable() {// 匿名内部类
@Override
public void func() {
System.out.println("func().....");
}
};
innerable.func();
}
}