• 交友须带三分侠气,作人要存一点素心。——《小窗幽记》

  • [译文] :交朋友应该带着三分侠义气,做人要保存一点素净心。

内部类

内部类的精华: 匿名内部类, 而匿名内部类的作用恰恰就是简化了代码, 让我们可以少些一个类(实现类)

内部类说白了, 就是类中嵌套定义一个类.虽然方法与方法之间不能嵌套定义, 但是类却能够嵌套, 类似于

1
2
3
4
5
6
public class Car{
//内部类
public class Engine{

}
}

成员内部类

就是在类中成员变量/成员方法的位置, 定义了一个内部类, 类似于成员变量、成员方法。

代码举例:

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 Outer {
private int age = 99;
public static String a="小王";

// 成员内部类
public class Inner{
private String name;
private int age = 88;

//在内部类中既可以访问自己类的成员,也可以访问外部类的成员
public void test(){
System.out.println(age); //88
System.out.println(a); //小王

int age = 77;
System.out.println(age); //77
System.out.println(this.age); //88
System.out.println(Outer.this.age); //99
}

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;
}
}
}

注意事项:

  • 成员内部类的创建格式为:
1
2
3
4
//外部类.内部类 变量名 = new 外部类().new 内部类();
Outer.Inner in = new Outer().new Inner();
//调用内部类的方法
in.test();
  • 内部类访问成员的特点

    • 既可以访问内部类成员、也可以访问外部类成员
    • 如果内部类成员和外部类成员同名,可以使用**外部类名.this.成员**区分

静态内部类

静态内部类,其实就是在成员内部类的前面加了一个static关键字。静态内部类属于外部类自己持有。

代码举例

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Outer {
private int age = 99;
public static String name = "小威";

// 静态内部类
public static class Inner{
// 静态内部类访问外部类的静态变量可以, 但是访问不了实例变量
public void test(){
System.out.println(name);
//System.out.println(age); // 报错
}
}
}

注意事项

  • 定义静态内部类,就是在定义成员内部类的位置定义,顺便多了一个Static修饰符。
  • 定义静态内部类,内部类只能访问外部类的静态变量,但是不能访问外部类的实例变量。
  • 创建内部类的对象的格式为:
1
2
3
//格式:外部类.内部类 变量名 = new 外部类.内部类();
Outer.Inner in = new Outer.Inner();
in.test();

局部内部类

局部内部类是定义在方法中的类,和局部变量一样,只能在方法中有效。所以局部内部类的局限性很强,一般在开发中是不会使用的。

代码举例

1
2
3
4
5
6
7
8
9
10
11
12
13
public class OuterPartInner {
public void test(){
class Inner{
public void show(){
System.out.println("Inner....show");
}
}

// 局部内部类只能在方法中创建对象,并使用
Inner in = new Inner();
in.show();
}
}

注意事项

  • 局部内部类(是在类中方法的局部变量位置定义的类)在外部创建不了对象

匿名内部类

精华:匿名内部类是1个继承了该类, 或者实现了该接口的匿名的子类对象.

代码举例

  • 当父类为抽象类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 父类
public abstract class Animal{
public abstract void cry();
}

// 测试类
public class Test {
public static void main(String[] args) {
// 这里后面new的部分,其实就是一个Animal的子类对象
Animal a = new Animal() {
@Override
public void cry() {
System.out.println("喵喵喵?-猫");
}
};
// 执行上面重写的cry方法
a.cry();
}
}
  • 当父类是接口
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
// 父类接口
public class A implements Swimming{
@Override
public void swim() {
System.out.println("A游泳.....");
}
}

// 测试类(说明:如果正常创建则必须写一个接口的实现类,改用使用匿名内部类的方法可以少些一个类)
public class Test {
public static void main(String[] args) {
// 1,在这里, 如果想成功调用go方法, 必须传递一个Swimming类型的实际参数
A a = new A();
go(a);

// 2, 使用swimming作为父亲的角色,创建匿名内部类对象,作为go方法的实际参数使用
go(new Swimming() {
@Override
public void swim() {
System.out.println("匿名内部类...游泳....");
}
});
}

public static void go(Swimming s){
// 面向s这个对象, 调用swim方法, 一定会调用成功, 因为接口中有抽象的swim方法
s.swim();
}
}

注意事项

  • 匿名内部类其实有隐含的多态的特性,Animal a = Animal子类对象
  • 匿名内部类在编写代码时没有名字,编译后系统会为自动为匿名内部类生产字节码,字节码的名称会以外部类$1.class的方法命名

image-20240122174306107

枚举

总结:枚举是一种特殊的,它的格式是:

1
2
3
public enum 枚举类名{
枚举项1,枚举项2,枚举项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
26
27
28
29
// 枚举类Sex.java
public enum Sex {
Man, Women;
}

// MySystem.java
public class MySystem {
public void recommend(Sex sex){
// 利用switch选择具体的性别, 从而推荐图书
switch (sex){
case Man:
System.out.println("都市修仙");
break;
case Women:
System.out.println("宫斗");
break;
}
}
}

// Test.java
public class Test {
public static void main(String[] args) {
MySystem s = new MySystem();
// 推荐小说
s.recommend(Sex.Man);
s.recommend(Sex.Women);
}
}

注意事项

  • 枚举中的前几个枚举都是对象,可以调用枚举类中的成员方法,成员变量。例如
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
public enum A {
// xyz三个变量就代表着A类型的三个对象, 且这三个对象不能修改地址值
X,Y,Z;
public void show(){
System.out.println("A...show...");
}
}

public class TestA {
public static void main(String[] args) {
// 1, 尝试new A
// A a = new A(); // 枚举不能直接创建对象
// 2, 可以通过枚举的名称直接获取里面提前准备好的对象
A.X.show();
A.Y.show();
A.Z.show();

System.out.println(A.X);
System.out.println(A.Y);
System.out.println(A.Z);
// compareTo方法:结果大于0, 说明第一个比第二个大, 结果小于0, 说明第一个比第二个小, 等于0就是相等
System.out.println(A.X.compareTo(A.Z));

// Enum的静态方法, 可以将一个字符串转成指定类型的枚举对象
A y = Enum.valueOf(A.class, "Y");
y.show();
}
}

泛型

  • 泛型的好处:在编译阶段可以避免出现一些非法的数据。

  • 泛型的本质:把具体的数据类型传递给类型变量。

泛型概述

所谓泛型指的是,在定义类、接口、方法时,同时声明了一个或者多个类型变量(如:<E>),称为泛型类、泛型接口、泛型方法、它们统称为泛型。

Tips:

  • 注意泛型的<>里面可以写<E, K, T, V>等表示泛型的字母(Element, Key, Value, Type)
  • 泛型可以修饰类, 接口 和 方法(一般是在类名, 接口名的后面加上<E,K,T,V>, 但是泛型方法是在返回值类型前面写的)
  • 比如ArrayList类就是一个泛型类

确定泛型值的时机(※)

泛型类

  • 1: 在设计实现类的时候,直接写死;
  • 2: 设计实现类的时候也带泛型,将来创建实现类对象的时候,确定泛型的值;

泛型接口

泛型方法及泛型通配符

泛型类

就是在类名的后面加上<E,K,T,V>这种,像ArrayList <E>这样的,但是自己定义泛型类是非常少的。

自定义泛型类的格式如下

1
2
3
4
//这里的<T,W>其实指的就是类型变量,可以是一个,也可以是多个。
public class 类名<T,W>{

}

代码示例

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
// 自定义泛型类
public class Box<MoMo> {
// 把MoMo当成一个数据类型使用
private MoMo element;
// JavaBean类(实体类, pojo类
public MoMo getElement() {
return element;
}

public void setElement(MoMo element) {
this.element = element;
}

public Box() {
}

public Box(MoMo element) {
this.element = element;
}
}

// 测试类
public class Test {
public static void main(String[] args) {
// 1, 创建Box对象, 指定泛型为String
Box<String> box = new Box<>("abc");

// 2, 获取数据
String element = box.getElement();
System.out.println(element);
}
}

注意事项

  • 泛型类的类型确定可以在实例化对象的时候确定,也可以在设计泛型类的时候直接填写

泛型接口

泛型接口其实指的是在接口中把不确定的数据类型用<类型变量>表示。定义格式如下:

1
2
3
4
//这里的类型变量,一般是一个字母,比如<E>
public interface 接口名<类型变量>{

}

比如,我们现在要做一个系统要处理学生和老师的数据,需要提供2个功能,保存对象数据、根据名称查询数据,要求:这两个功能处理的数据既能是老师对象,也能是学生对象。

首先我们得有一个学生类和老师类

1
2
3
public class Teacher{

}
1
2
3
public class Student{

}

我们定义一个Data<T>泛型接口,T表示接口中要处理数据的类型。

1
2
3
4
5
public interface Data<T>{
public void add(T t);

public ArrayList<T> getByName(String name);
}

接下来,我们写一个处理Teacher对象的接口实现类

1
2
3
4
5
6
7
8
9
10
11
//此时确定Data<E>中的E为Teacher类型,
//接口中add和getByName方法上的T也都会变成Teacher类型
public class TeacherData implements Data<Teacher>{
public void add(Teacher t){

}

public ArrayList<Teacher> getByName(String name){

}
}

接下来,我们写一个处理Student对象的接口实现类

1
2
3
4
5
6
7
8
9
10
11
//此时确定Data<E>中的E为Student类型,
//接口中add和getByName方法上的T也都会变成Student类型
public class StudentData implements Data<Student>{
public void add(Student t){

}

public ArrayList<Student> getByName(String name){

}
}

再啰嗦几句,在实际工作中,一般也都是框架底层源代码把泛型接口写好,我们实现泛型接口就可以了。

泛型接口的类型传递顺序

image-20240122183248964

泛型方法

泛型方法的定义相对于泛型类和泛型接口有些特殊,泛型方法是在方法返回值前面定义的。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 泛型方法
public static <T> void test(T t){
return t;
}

// 测试类
public class Test{
public static void main(String[] args){
//调用test方法,传递字符串数据,那么test方法的泛型就是String类型
String rs = test("test");

//调用test方法,传递Dog对象,那么test方法的泛型就是Dog类型
Dog d = test(new Dog());
}

//这是一个泛型方法<T>表示一个不确定的数据类型,由调用者确定
public static <T> test(T t){
return t;
}
}

注意事项

  • 上面的代码,泛型方法两种返回值类型需要区别一下

泛型通配符

  • 表示任意类型
  • 表示是否继承自后面的类
  • 表示是否是后面类的爹

代码示例

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 class Test {
public static void main(String[] args) {
// 不允许以多态的形式操作泛型
ArrayList<Object> list = new ArrayList<>();
// Integer 的父类是Number, Number的父类是object
ArrayList<Integer> integer = new ArrayList<>();
ArrayList<Number> number = new ArrayList<>();
ArrayList<Object> object = new ArrayList<>();

m1(integer);
m1(number);

m2(number);
m2(object);
}
// 该方法的形参, 计划接受前两种集合
public static void m1(ArrayList<? extends Number> list){

}

// 该方法的形参, 计划接受后两种集合
public static void m2(ArrayList<? super Number> list){

}
}

注意事项

泛型通配符的取值范围通常是闭区间,就是能够取到后面的数据类型

泛型擦除

也就是说泛型只能编译阶段有效,一旦编译成字节码,字节码中是不包含泛型的。而且泛型只支持引用数据类型,不支持基本数据类型。

image-20240122184507590

反编译后

image-20240122184531359