面向对象高级

1、接口interface

1、接口中可以有什么?

  1. jdk8之前,只能有:

    1. 公共的静态的常量:其中 public static final 可以省略
    2. 公共的抽象的方法:其中 public abstract 可以省略
  2. jdk8及以后:还可以声明默认方法(用default修饰,不能省略default也不能省略方法体)和静态方法

    (接口中声明的静态方法只能使用“接口名.”进行调用,不能通过实现类的对象进行调用)

  3. (9.0新增了私有方法)

  4. 没有构造器,没有初始化块

2、java类不能多继承,可以多实现

c++可以多继承,java一个类只能继承一个父类。为了弥补这个问题,java支持多实现,即一个类可以实现多个接口。

接口不能创建对象,但是可以被类实现(implements ,类似于被继承)实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements 关键字

1
2
3
4
5
6
7
// 一般先写继承再写实现
class A extends SuperA implements B,C{
// 重写接口中抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
//重写时,default 单词就不要再写了,它只用于在接口中表示默认方法,到类中就没有默认方法的概念了
//接口中的静态方法不能被继承也不能被重写
}

3、接口可以多继承

接口与接口是继承关系,而且可以多继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public interface AA {
void method1();
}
public interface BB {
void method2();
}
//定义子接口
public interface CC extends AA,BB {
void eat();
}
//定义子接口的实现类
public class type implements CC {
@Override
public void eat() {
System.out.println("阿巴阿巴阿巴\n");
}
@Override
public void method2() {
System.out.println("阿巴阿巴阿巴\n");
}
@Override
public void method1() {
System.out.println("阿巴阿巴阿巴\n");
}
}

4、接口与实现类对象构成多态引用

实现类实现接口,类似于子类继承父类,因此,接口类型的变量与实现类的对象之间,也可以构成多态引用。通过接口类型的变量调用方法,最终执行的是我们new的实现类对象实现的方法体。

5、接口中属性和方法的调用

1.接口的静态成员

​ 接口不能直接创建对象,但是可以通过接口名直接调用接口的静态方法和静态常量。

2.接口的静态方法

​ 接口中声明的静态方法只能使用“接口名.”进行调用,不能通过实现类的对象进行调用

3.接口的抽象/默认方法

  • 对于接口的抽象方法、默认方法,只能通过实现类对象才可以调用
    • 接口不能直接创建对象,只能创建实现类的对象

6、jdk8冲突问题

6.1 默认方法冲突问题

(1)类优先原则

​ 当一个类,既继承一个父类,又实现若干个接口时,父类中的成员方法与接口中的抽象方法重名,子类就近选择执行父类的成员方法。

​ 如果子类重写了这个方法,调用父类用super.,调用某个接口的那个同名方法用接口名.super.方法名()

(如果那个接口中那个方法是静态的可以直接接口名.方法名()

2、内部类

1、创建内部类的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person {
//静态的成员内部类
static class dog {
}
//非静态的成员内部类
class bird {
}
}
public class OuterClassTest {
//创建Person的静态的成员内部类的实例
Person.Dog dog = new Person.Dog();
//创建Person的非静态的成员内部类的实例
Person p1 = new Person();
Person.Bird bird = p1.new Bird();

}

2.内部类命名冲突

其实不算是冲突,只是覆盖了,能正常编译运行的。

1.属性命名冲突

1
2
3
4
5
6
7
8
9
10
11
12
Class Person {
String name = "Tom";
int age;

class Bird {
String name = "企鹅";
public void show(String name) {
System.out.println("age = " + this.name);//调用它自己的name
System.out.println("age = " + Person.this.name);//调用父类的name
}
}
}

2.方法命名冲突

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Class Person {
public void eat() {
System.out.println("人吃蘑菇");
}

class Bird {
String name = "企鹅";
public void eat() {
System.out.println("企鹅不吃萝卜");
}
public void show() {
eat();//企鹅不吃萝卜
this.eat();//企鹅不吃萝卜
Person.this.eat();//人吃蘑菇
}
}
}

3、匿名类

1. 提供一个继承与Object的匿名子类的匿名对象

1
2
3
4
5
6
7
8
9
10
public class InnerClassTest1 {
public static void main(String[] args) {
new Object() {
public void whoami() {
System.out.println("id = 1000, name = \"QAQ\"" + "\n");
}
}.whoami();
System.out.println("ciallo~");
}
}

4、枚举类

1、jdk5.0之前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class Season {
//2.声明当前类的对象的实例变量,用private final修饰
private final String seasonName;
private final String seasonDesc;
//1.私有化类的构造器
private Season(String seasonName, String seasonDesc) {
this.seasonDesc = seasonDesc;
this.seasonName = seasonName;
}

//3.提供实例变量的get方法

public String getSeasonName() {
return seasonName;
}

public String getSeasonDesc() {
return seasonDesc;
}
//4.创建当前类的实例,需要用public static final修饰
public static final Season SPRING = new Season("清明宜晴","谷雨宜雨");
public static final Season SUMMER = new Season("白露种高山","秋分种平原");
public static final Season AUTUMN = new Season("枯骨生荒草","丘壑化桑田");
public static final Season WINTER = new Season("把你种进土里","你重新长吧");

// @Override
// public String toString() {
// return "Season{" +
// "seasonName='" + seasonName + '\'' +
// ", seasonDesc='" + seasonDesc + '\'' +
// '}';
// }
}
class Main {
public static void main(String[] args) {
System.out.println(Season.SPRING);
}
}

当我们在Java中使用System.out.println()打印对象时,实际上会调用该对象的toString()方法来获取其字符串表示形式,并将其输出到控制台。

Java中的每个类都继承了Object类,而Object类中包含了一个默认的toString()方法的实现。这个默认实现返回的是类名和对象的哈希码的组合,形式为ClassName@HashCode

一般在自己的类中重写toString()方法,如此可以根据自己的需求定义对象的字符串表示形式,以便更好地理解和调试你的代码。这在调试和日志记录时特别有用,因为你可以更清楚地了解对象的状态和内容。

2、jdk5.0新增了enum

使用enum关键字定义的枚举类,默认其父类是java.lang.Enum类

使用enum关键字定义的枚举类,不要显示地定义其父类,否则报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class SeasonTest2 {
public static void main(String[] args) {
System.out.println(SeasonTest1.WINTER);
}
}
enum SeasonTest1 {
//1.必须在枚举类的开头声明多个对象,对象之间用逗号而非分号隔开
SPRING("清明宜晴","谷雨宜雨"),
SUMMER("白露种高山","秋分种平原"),
AUTUMN("枯骨生荒草","丘壑化桑田"),
WINTER("把你种进土里","你重新长吧");

//2.声明当前类的对象的实例变量,用private final修饰
private final String seasonName;
private final String seasonDesc;
//3.私有化类的构造器
private SeasonTest1(String seasonName, String seasonDesc) {
this.seasonDesc = seasonDesc;
this.seasonName = seasonName;
}
//4.提供实例变量的get方法

public String getSeasonName() {
return seasonName;
}

public String getSeasonDesc() {
return seasonDesc;
}


// @Override
// public String toString() {
// return "Season{" +
// "seasonName='" + seasonName + '\'' +
// ", seasonDesc='" + seasonDesc + '\'' +
// '}';
// }
}

枚举常量已经预定义了,因此不需要创建对象。枚举常量本身就是枚举类型的实例(也就是说她们本身就是对象)。打印枚举常量时,默认情况下会调用枚举类的toString()方法。

对于枚举类型,默认的toString()方法已经被重写,它返回枚举常量的名称,而不是类名和哈希码的组合。因此,在代码中,当打印SeasonTest1.WINTER时,它只会打印枚举常量的名称,即WINTER

3、enum类的常用方法

1.toString()

String toString()返回当前枚举常量的名称

2.name()

String name()得到当前对象名称

3.values()

static 枚举类型[] values()

返回枚举类型的对象数组,该方法可以便捷地遍历所有的枚举值

1
2
3
4
5
6
//枚举类定义沿用上一个代码块中的SeasonTest1
SeasonTest1[] values1 = SeasonTest1.values();
for(int i = 0; i < values1.length; i++) {
System.out.println(values1[i]);
//把枚举类的所有对象都放到这个数组里面了,打印的话调用toString()方法
}

4.valueOf(String sbjName)

返回当前枚举类中名称为objName的枚举类对象

如果枚举类中不存在objName名称的对象,则报错

1
2
3
String objName = "WINTER";
SeasonTest1 season1 = SeasonTest1.valueOf(objName);
//去找叫WINTER的枚举常量然后返回给左值season1

5.name()

String name()得到当前枚举常量的名称,建议优先使用toString()

6.ordinal()

int ordinal()

返回当前枚举常量的次序号,从0开始。

4、实现接口的枚举类

  • 和普通 Java 类一样,枚举类可以实现一个或多个接口
  • 若每个枚举值在调用实现的接口方法呈现相同的行为方式,则只要统一实现该方法即可。
  • 若需要每个枚举值在调用实现的接口方法呈现出不同的行为方式,则可以让每个枚举值分别来实现该方法

5、开发中常用的写法

以下为同一个包的三个文件

1
2
3
public enum Season3 {
SPRING,SUMMER,AUTUMN,WINTER;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class XI {
private Season3 season3;
private int age;
//构造器
public XI() {
}

public XI(Season3 season3, int age) {
this.season3 = season3;
this.age = age;
}

public Season3 getSeason3() {
return season3;
}

public void setSeason3(Season3 season3) {
this.season3 = season3;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "xi{" +
"season3=" + season3 +
", age=" + age +
'}';
}
}

1
2
3
4
5
6
public class Enum2Test {
public static void main(String[] args) {
XI xibao = new XI(Season3.SPRING,37);
System.out.println(xibao);
}
}

5、 JUnit单元测试

1、需要的jar包

1
2
junit-4.12.jar
hamcrest-core-1.3.jar

2.正确的单元测试方法

  1. 所在类必须是公共的、非抽象的,且包含唯一的无参构造器(其实不写构造器就符合这个要求了)
  2. @Test标记的方法本身必须是公共的、非抽象的、非静态的,无返回值,无参数的

​ 但是对于它调用的方法没有那么多限制

3.设置执行JUnit用例时支持控制台输入

工具栏:help - Edit Custom VM Options

在结尾加上

1
-Deditable.java.test.console=true

添加完成之后,重启IDEA即可。

如果上述位置设置不成功,需要继续修改如下位置

修改位置1:IDEA安装目录的bin目录(例如:D:\develop_tools\IDEA\IntelliJ IDEA 2022.3.2\bin)下的idea64.exe.vmoptions文件。

修改位置2:C盘的用户目录C:\Users\用户名\AppData\Roaming\JetBrains\IntelliJIdea2022.3 下的idea64.exe.vmoptions`件。

4.定义test测试方法模板

设置-Editor-Live Template 点击”+”来定义模板。

(建议先新增组再新增模板)

image-20240314144912309

$var1$ 模版引入时光标的位置

$END$敲回车之后光标的位置

记得点下面蓝色的 Define按钮更改应用范围