什么是 OOP

面向对象编程(Object-Orien Programming,OOP)

  • 面向过程:编写对数据执行的过程或方法
  • 面向对象:创建同时包含数据和方法的对象

类和对象:

  1. 类是对象的模板,而对象是类的实例
  2. 创建各个对象时,它们会从类中继承所有变量和方法

类的构成:

  1. 成员变量(属性)
  2. 构造方法
  3. 成员方法
  4. 代码块
  5. 内部类

**类的分类: **

  1. Javabean类:用来描述一类事务的类,不写main方法
  2. 测试类:程序的主入口,编写main方法,在测试类中创建Javabean类的对象并进行赋值调用
  3. 工具类:不描述事物,帮我们做一些事情

创建类:class (大驼峰,java 文件的名称应与类名匹配)

1
2
3
4
pulic class Person {
public String name;
public int age;
}
  • 一个文件只能一个类是 public 修饰,public 类名必须是文件名

JavaBean

  1. 类名需要见名知意
  2. 成员变量使用private修饰
  3. 提供至少两个构造方法
    • 无参构造方法
    • 带全部参数的构造方法
  4. 成员方法:
    • 提供每一个成员变量对应的get和set方法
    • 如果还有其他行为,也需要写上

工具类

  1. 类名见名知意
  2. 私有化构造方法
  3. 方法都用 public static 修饰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ArrayUtil {
private ArrayUtil() {}

public static String printArr(int[] arr) {
StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i = 0; i < arr.length; i++) {
if (i == arr.length - 1) {
sb.append(arr[i]);
} else {
sb.append(arr[i]).append(", ");
}
}
sb.append("]");
return sb.toString();
}
}

测试类

public static void main(String[] args) {}

1
2
3
4
5
6
7
public class TestDemo {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
String str = ArrayUtil.printArr(arr);
System.out.println(str);
}
}

对象

创建对象:new

1
Person ming = new Person();

**对象的属性:**访问、修改:.

方法

方法(method)是程序中最小的执行单元

定义

1
2
3
4
修饰符 返回类型 方法名(参数列表) {
方法体;
return 方法返回值;
}

修饰符:

  • private:只能在类内部调用
  • static:静态方法,属于类本身,不需要创建对象也能调用
  • public:任何类可访问(常用的还是 public static ...

**this:**指向当前实例

可变参数:类型...

1
2
3
4
5
6
class Group {
private String[] names;
public void setNames(String... names) {
this.names = names;
}
}

按值传递 vs 引用传递:

类型 传递
基本数据类型 按值传递(传递真实数据)
引用类型(数组、对象) 引用传递(传递地址值)

构造方法

快捷键:alt+insert

作用:创建对象时,虚拟机调用构造方法,初始化成员变量

  • 创建对象时由虚拟机调用,不能手动调用
  • 每创建一次对象,调用一次

要求:

  1. 方法的名称是类名
  2. 没有返回值(没有 void,不能写 return
  3. 调用:new
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Person {
private String name;
private int age;

public Person() { //无参构造
System.out.println("success");
}

public Person(String name, int age) { //有参构造
this.name = name;
this.age = age;
}

public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
}
  • 如果没有定义构造方法,系统将给出一个默认的无参数构造方法
  • 如果定义了构造方法,系统将不再提供默认的构造方法(不管用不用,都写上

重载

重载 overload:

  1. 同一个类,方法名相同(小驼峰)
  2. 参数列表不同:数量、类型、顺序
  3. 返回值可不同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Hello {
public void hello() {
System.out.println("Hello, world!");
}
public void hello(String name) {
System.out.println("Hello, " + name + "!");
}
public void hello(String name, int age) {
if (age < 18) {
System.out.println("Hi, " + name + "!");
} else {
System.out.println("Hello, " + name + "!");
}
}
}

方法链

要求:

  1. 前一个方法返回类本身 this
  2. 或者是构造函数

修饰符

  • 访问控制修饰符 - 控制访问级别
  • 非访问控制修饰符 - 不控制访问级别,但提供其他功能

访问修饰符

  • 对于

    修饰符 作用
    public 所有类都能访问
    默认不写 同包内可访问
  • 对于属性、方法和构造函数:从小到大

    修饰符 作用
    private 仅限当前类可访问
    默认不写 同包内可访问
    protected 同包子类可访问
    public 所有类都能访问

实际开发中,一般只用private和public

  • 成员变量私有
  • 方法公开

特例:如果方法中的代码是抽取其他方法中共性代码,这个方法一般也私有

非访问修饰符

  • 对于

    修饰符 作用
    final 表示类不可继承
    abstract 表示类是抽象类,不能实例化
  • 对于属性和方法

    修饰符 作用
    final 属性和方法不能被重写、修改(常量
    static 属性方法属于类,而不是实例
    abstract 只能在抽象类的抽象方法中使用,方法没有主体
    transient 属性和方法不会被序列化
    synchronized 方法一次只能由一个线程访问
    volatile 属性的值不在线程本地缓存,而是始终从“主内存”读取

static

static-intro

static

static-memory

重新认识main方法

main-method

final

final

包、代码块、作用域

package

导入类:import java.util.Scanner;

导入整个包:import java.util.*;(不包括子包的 class

代码块

  1. 局部代码块
  2. 构造代码块
  3. 静态代码块

局部代码块:

code-block1

构造代码块:调用构造函数时执行

code-block2

静态代码块:

  • 格式:static{}
  • 特点:需要通过static关键字修饰,随着类的加载而加载,并且自动触发、只执行一次
  • 使用场景:在类加载的时候,做一些数据初始化的时候使用

code-block3

作用域

**类作用域:**在整个类中可访问

**方法作用域:**只能在该方法中访问

**块作用域:**变量只能在块内访问

**循环作用域:**变量只能在循环体内访问

**包作用域:**同包内可访问(没用任何修饰符 publicprivateprotected

封装

对象代表什么,就得封装对应的数据,并提供数据对应的行为

封装:确保对用户敏感的数据进行隐藏

  • private:声明变量/属性(只能在同一个类中访问,外部类无权访问)
  • GetSet 方法:访问、更新 private 变量

Get Set

get 方法返回变量值,set 方法设置值

两者的语法是它们都以 getset 开头,后跟变量的名称,第一个字母为大写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Student {
private String name;
private int age;

public void setName(String name) {
this.name = name;
}

public String getName() {
return name;
}

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

public int getAge() {
return age;
}
}

this

this 修饰的变量用于指代成员变量,主要作用是(区分局部变量和成员变量的重名问题)

重名问题:就近原则 -> 局部变量 -> 成员变量

this

意义

  • 更好地控制类属性和方法
  • 类属性可以是只读的(如果只使用 get 方法),也可以是只写的(如果只使用 set 方法)
  • 灵活:程序员可以更改代码的一部分,而不会影响其他部分
  • 提高数据安全性

继承

特点:

  • Java只支持单继承发,不支持多继承,但支持多层继承
  • 每一个类都直接或者间接的继承于 Object

格式

extends:class 子类 extends 父类 {}

1
2
3
4
5
6
7
class 父类 {
...
}

class 子类 extends 父类 {
...
}
  • 子类:从另一个类继承的类
  • 父类:被继承的类

修饰符

修饰符:

  • private:子类无法访问父类的属性、方法
  • protected:子类可访问父类的属性、方法
  • final:其他类无法继承

extends

注意:能否继承 不等于 能否访问

  • 子类不能继承父类的构造方法
  • 子类可以继承父类的私有成员(成员变量,方法),只是子类无法直接访问而已,可以通过getter/setter方法访问父类的private成员变量

内存分析

构造方法:无法继承

成员变量:(方法区省略 Object.class)

extends-memory

成员方法:

extends-method

extends-virmethod

super

super表示父类对象的引用

继承中成员变量的访问特点:

extend-variable

继承中构造方法的访问特点:

extends-constructor

重写

**要求:**建议写注解 @override

  1. 方法名、参数列表(建议:返回类型相同)
  2. 访问权限子类必须大于等于父类(空着不写 < protected < public)
  3. 返回值类型子类必须小于等于父类
  4. 只有被添加到虚方法表中的方法才能被重写

重写的本质:

override

重写 vs 重载:

重写:

1
2
3
4
5
6
7
public class Processor {
public void process(int i, int j) { /* ... */ }
}
class MathProcessor extends Processor {
@Override
public void process(int i, int j) { /* ... */ }
}

重载:

1
2
3
4
5
6
public class Processor {
public void process(int i, int j) { /* ... */ }
public void process(int[] ints) { /* ... */ }
public void process(Object[] objs) { /* ... */ }

}

多态

同类型的对象,表现出不同的形态

形式

表现形式:父类类型 对象名称 = 子类对象;

前提:

  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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class Person {
private String name;
private int age;

public void show(){
System.out.println(name + ", " + age);
}
}

public class Administrator extends Person {
@Override
public void show() {
System.out.println("管理员的信息为:" + getName() + ", " + getAge());
}
}

public class Student extends Person{
@Override
public void show() {
System.out.println("学生的信息为:" + getName() + ", " + getAge());
}
}

public class Teacher extends Person{
@Override
public void show() {
System.out.println("老师的信息为:" + getName() + ", " + getAge());
}
}

public class Test {
public static void main(String[] args) {
Student s = new Student();
s.setName("张三");
s.setAge(18);

Teacher t = new Teacher();
t.setName("王建国");
t.setAge(30);

Administrator admin = new Administrator();
admin.setName("管理员");
admin.setAge(35);

register(s);
register(t);
register(admin);
}
public static void register(Person p){
p.show();
}
}

调用特点

调用成员的特点:

  • 变量调用:编译看左边,运行看左边
  • 方法调用:编译看左边,运行看右边
1
2
3
4
5
6
7
Fu f = new Zi();
//编译看左边的父类中有没有name这个属性,没有就报错
//在实际运行的时候,把父类name属性的值打印出来
System.out.println(f.name);
//编译看左边的父类中有没有show这个方法,没有就报错
//在实际运行的时候,运行的是子类中的show方法
f.show();
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
40
41
42
43
44
public class Test {
public static void main(String[] args) {
//创建对象(多态方式)
//Fu f = new Zi()
Animal a = new Dog();
//调用成员变量:编译看左边,运行也看左边
//编译看左边:javac编译代码的时候,会看左边的父类中有没有这个变量,如果有,编译成功,如果没有编译失败
//运行也看左边:java运行代码的时候,实际获取的就是左边父类中成员变量的值
System.out.println(a.name);//动物

//调用成员方法:编译看左边,运行看右边
//编译看左边:javac编译代码的时候,会看左边的父类中有没有这个方法,如果有,编译成功,如果没有编译失败
//运行看右边:java运行代码的时候,实际上运行的是子类中的方法
a.show();//Dog --- show 方法

//理解:
//Animal a = new Dog()
//现在用a去调用变量和方法
//而a是Animal类型的,所以默认都会从Animal这个类中去找
//成员变量:在子类的对象中,会把父类的成员变量也继承下来。父:name 子:name
//成员方法:如果子类对方法进行了重写,那么在虚方法表中是会把父类的方法进行覆盖的。
}
}

class Animal{
String name = "动物";
public void show() {
System.out.println("Animal --- show 方法");
}
}
class Dog extends Animal{
String name = "狗";
@Override
public void show() {
System.out.println("Dog --- show 方法");
}
}
class DCat extends Animal{
String name = "猫";
@Override
public void show() {
System.out.println("Cat --- show 方法");
}
}

内存分析

polymorphism-memory

优势和弊端

polymorphism-advan

弊端的解决方案:强制转型

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package com.itheima;

public class Test {
public static void main(String[] args) {
//创建对象
Animal a = new Dog();
//编译看左边,运行看右边
a.eat();

//多态的弊端
//不能调用子类的特有功能
//a.lookHome();报错
//报错的原因?
//当调用成员方法的时候,编译看左边,运行看右边
//那么在编译的时候会先检查左边的父类中有没有这个方法,如果没有直接报错

//解决方案:
//变回子类类型就可以了
//细节:转换的时候不能瞎转,如果转成其他类的类型,就会报错 Cat c = (Cat) a;
Dog dog = (Dog) a;
dog.loonHome();

// if (a instanceof Dog) {
// Dog d = (Dog) a;
// d.loonHome();
// } else if (a instanceof Cat) {
// Cat c = (Cat) a;
// c.catchMouse();
// } else {
// System.out.println("没有这个类型,无法转换");
// }

//新特性
//先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d
//如果不是,则不强转,结果直接是false
if (a instanceof Dog d) {
d.loonHome();
} else if (a instanceof Cat c) {
c.catchMouse();
} else {
System.out.println("没有这个类型,无法转换");
}
}
}

class Animal{
public void eat() {
System.out.println("动物在吃东西");
}
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头");
}
public void loonHome() {
System.out.println("狗看家");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫在吃小鱼干");
}
public void catchMouse() {
System.out.println("猫抓老鼠");
}
}

转型

向上转型:Person p = new Student();

  1. pPerson 类型的引用变量,指向 Student 对象(实例)
  2. 编译时是 Person,运行时是 Student

向下转型:

1
2
3
4
Person p1 = new Student(); // upcasting, ok
Person p2 = new Person();
Student s1 = (Student) p1; // ok
Student s2 = (Student) p2; // runtime error! ClassCastException!
  • Person 类型 p1 实际指向 Student 实例:成功

  • Person 类型变量 p2 实际指向 Person 实例:失败,子类功能比父类多

**instanceof:**判断变量指向的实例是否是指定类型,或者这个类型的子类,若是可直接转型

1
2
3
4
5
6
7
8
9
public class Main {
public static void main(String[] args) {
Object obj = "hello";
if (obj instanceof String s) {
// 可以直接使用变量s:
System.out.println(s.toUpperCase());
}
}
}

**多态:**真正执行的方法取决于运行时期实际类型的方法

抽象

抽象方法:将共性的方法抽取到父类后,由于每个子类执行的内容不一样,所以在父类中不能确定具体的方法体。该方法就可以定义为抽象方法

抽象类:如果一个类中存在抽象方法,那么该类就必须声明为抽象类

定义

  • 抽象方法:public abstract 返回值类型 方法名(参数列表);
  • 抽象类:public abstract class 类名{}

特点

  • 抽象类不能实例化(不能创建对象)
  • 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
  • 可以有构造方法
  • 抽象类的子类:
    1. 要么重写抽象类中的所有抽象方法
    2. 要么是抽象类
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
40
41
42
43
44
45
public class Main {
//创建对象
//Person p = new Person(); 报错
Student student = new Student("ZhangSan", 2);
}

abstract class Person {

private String name;
private int age;

//抽象类可以写构造方法
//作用:当创建子类对象时,给属性进行赋值
public Person() {
}

public Person(String name, int age) {
this.name = name;
this.age = age;
}

public abstract void work();

public void sleep() {
System.out.println("Sleeping...");
}

}

class Student extends Person {

public Student() {

}

public Student(String name, int age) {
//交给父类,来为对象赋值
super(name, age);
}

@Override
public void work() {
System.out.println("Student working...");
}
}

意义

同一代码规范

abstract-meaning1

abstract-meaning2

接口

抽象:定义一类事物

接口:定义一类行为(接口就是一种规则,是对行为的抽象)

定义和使用

  • 定义接口:public interface 接口名{}
  • 接口不能实例化
  • 类实现接口:
    1. 单实现:public class 类名 implements 接口名 {}
    2. 多实现:public class 类名 extends 父类 implements 接口名1, 接口名2 {}
  • 接口的子类(实现类):
    1. 要么重写接口中的所有抽象方法
    2. 要么是抽象类

成员特点

  • 成员变量:

    1. 只能是常量
    2. 默认修饰符:public static final(即使不写,Java也会帮你带上)
  • 构造方法:没有

  • 成员方法:

    1. 只能是抽象方法
    2. 默认修饰符:public abstract
  • JDK7以前:接口中只能定义抽象方法(主)

    JDK8的新特性:接口中可以定义有方法体的方法

    JDK9的新特性:接口中可以定义私有方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface InterF {
// 抽象方法!
// public abstract void run();
void run();

// public abstract String getName();
String getName();

// public abstract int add(int a , int b);
int add(int a , int b);


// 它的最终写法是:
// public static final int AGE = 12 ;
int AGE = 12; //常量
String SCHOOL_NAME = "DUT";

}

接口和类之间的关系

  • 类和类之间得关系:

    继承关系,只能单继承,不能多继承,但是可以多层继承

  • 类和接口的关系:

    实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口

  • 接口和接口的关系:

    继承关系,可以单继承,也可以多继承

新增方法

默认方法

interface-default1

interface-default2

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
40
41
42
43
public class Main {
public static void main(String[] args) {
//创建类的对象
InterImpl ii = new InterImpl();
ii.method();
ii.show();
}
}

interface InterA {
/*
接口中默认方法的定义格式:public default 返回值类型 方法名(参数列表){}

接口中默认方法的注意事项:
1.默认方法不是抽象方法,所以不强制被重写。但是如果被重写,重写的时候去掉default关键字
2.public可以省略,default不能省略
3.如果实现了多个接口,多个接口中存在相同名字的默认方法,子类就必须对该方法进行重写
*/

public abstract void method();

public default void show() {
System.out.println("InterA接口中的默认方法 --- show");
}
}
interface InterB {
public abstract void method();

public default void show() {
System.out.println("InterB接口中的默认方法 --- show");
}
}
class InterImpl implements InterA, InterB {
@Override
public void method() {
System.out.println("实现类重写的接口方法");
}

@Override
public void show() {
System.out.println("重写接口中的默认方法");
}
}

静态方法

interface-static

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 Main {
public static void main(String[] args) {
/*
接口中静态方法的定义格式:
格式:public static 返回值类型 方法名(参数列表){}
范例:public static void show()

接口中静态方法的注意事项:
静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
public可以省略,static不能省略
*/

//调用接口中的静态方法
Inter.show();

//调用实现类中的静态方法
InterImpl.show();
//子类把父类继承下来的虚方法表里面的方法进行覆盖了,这才叫重写
}
}

interface Inter {
public abstract void method();
public static void show() {
System.out.println("Inter接口中的静态方法");
}
}

class InterImpl implements Inter {
@Override
public void method() {
System.out.println("Inter接口中的静态方法");
}

//不叫重写
public static void show() {
System.out.println("....");
}
}

私有方法

为了解决默认方法、静态方法中重复的代码的问题

interface-private1

接口中私有方法的定义格式:

  • 格式1:private 返回值类型 方法名(参数列表) {}

    范例1:private void show() {}

  • 格式2:private static 返回值类型 方法名(参数列表) {}

    范例2:private static void method() {}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface InterA {
public default void show1() {
System.out.println("show1方法开始执行了");
show3();
}

public static void show2() {
System.out.println("show2方法开始执行了");
show4();
}

//普通的私有方法,给默认方法服务的
private void show3() {
System.out.println("记录程序在运行过程中的各种细节,这里有100行代码");
}

//静态的私有方法,给静态方法服务的
private static void show4() {
System.out.println("记录程序在运行过程中的各种细节,这里有100行代码");
}
}

interface-method

应用

interface-app1

interface-app2

适配器设计模式

  • 设计模式:(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。

  • 简单理解:设计模式就是写代码的各种套路

  • 适配器设计模式:解决接口与接口实现类之间的矛盾问题

interface-design

内部类

内部类:在一个类的里面,再定义一个类

使用场合:B类表示的事物是A类的一部分,且B单独存在没有意义

innerclass

innerclass-example

分类:

  1. 成员内部类
  2. 静态内部类
  3. 局部内部类
  4. 匿名内部类(重要)

成员内部类

innerclass1

**获取成员内部类对象: **

  1. 方式一:在外部类中编写方法,对外提供内部类的对象(private)
  2. 直接创建格式:外部类名.内部类名 对象名 = 外部类对象.内部类对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Main {
public static void main(String[] args) {
Outer.Inner1 in1 = new Outer().new Inner1();

Outer o = new Outer();
Object in2 = o.getInner2();
}
}

class Outer {
String name;

class Inner1 {

}

private class Inner2 {
static int a = 10;
}
public Inner2 getInner2() {
return new Inner2();
}
}

内部类获取外部类的成员变量:

innerclass2

静态内部类

静态内部类是一种特殊的成员内部类

innerclass-static1

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
40
public class Main {
public static void main(String[] args) {
//注意事项:
//1.静态内部类也是成员内部类的一种
//2.静态内部类只能访问外部类中的静态变量和静态方法
// 如果想要访问非静态的需要创建外部类的对象

//创建静态内部类对象的格式:外部类名.内部类名 对象名 = new 外部类名.内部类名();
//调用静态方法的格式:外部类名.内部类名.方法名();

//创建静态内部类的对象
//只要是静态的东西,都可以用类名点直接获取
Outer.Inner oi = new Outer.Inner();
oi.show1();

//静态方法
Outer.Inner.show2();
}
}

class Outer {
int a = 10;
static int b = 20;

static class Inner {
public void show1() {
System.out.println("非静态的方法被调用");
// Outer o = new Outer();
// System.out.println(o.a);
// System.out.println(b);
}

public static void show2() {
System.out.println("静态的方法被调用");
// Outer o = new Outer();
// System.out.println(o.a);
// System.out.println(b);
}
}
}

innerclass3

局部内部类

innerclass4

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
40
41
42
43
44
45
46
public class Main {
public static void main(String[] args) {
/*
局部内部类
1.将内部类定义在方法里面就叫做局部内部类,类似于方法里面的局部变量
2.外界是无法直接使用局部内部类,需要在方法内部创建对象并使用
3.该类可以直接访问外部类的成员,也可以访问方法内的局部变量
*/

//无法直接使用show方法里面的局部变量a
Outer o = new Outer();
//o.a报错

//调用show方法,执行代码
o.show();
}
}

class Outer {
int b = 20;

public void show() {
int a = 10;

//局部内部类
class Inner {
String name;
int age;

public void method1() {
System.out.println(a);
System.out.println(b);
System.out.println("局部内部类中的method1方法");
}
public static void method2() {
System.out.println("局部内部类中的method2方法");
}
}

Inner i = new Inner();
System.out.println(i.name);
System.out.println(i.age);
i.method1();
Inner.method2();
}
}

匿名内部类

本质:不是类,而是一个类的子类对象/接口的实现类对象,只不过隐藏了名字

innerclass5

innerclass7

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public class Main {
public static void main(String[] args) {
/*格式:
new 类名或者接口名() {
重写方法;
};
*/

//编写匿名内部类的代码
new Animal() {
@Override
public void eat() {
System.out.println("重写了eat的方法");
}
};

//在测试类中调用下面的method方法?
//以前的方式:自己写一个子类继承Animal类,再创建子类的对象,传递给method方法
// Dog d = new Dog();
// d.eat();
//如果Dog类只用一次,那么还需要定义一个类太麻烦了

method(
new Animal() {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
);

//整体我们可以理解为Swim接口的实现类对象
//接口多态
Swim s = new Swim() {
@Override
public void swim() {
System.out.println("重写了Swim的方法");
}
};
//编译看左边,运行看右边
s.swim();

new Swim() {
@Override
public void swim() {
System.out.println("重写了Swim的方法");
}
}.swim();
}

public static void method(Animal a) { //Animal a = 子类对象 -> 多态
a.eat();// 编译看左边,运行看右边
}

}

interface Swim {
public abstract void swim();
}

abstract class Animal {
public abstract void eat();
}

class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}

innerclass6

核心类

枚举类

enum:enum 类型就是 class (引用类型)

  • 继承自java.lang.Enum,且无法被继承
  • 只能定义 enum 的实例,无法通过 new 创建
  • 定义的每个实例都是引用类型的唯一实例
  • 可用于 switch 语句

创建:

1
2
3
public enum Color {
RED, GREEN, BLUE;
}

编译出的 class 文件:

1
2
3
4
5
6
7
8
public final class Color extends Enum { // 继承自Enum,标记为final class
// 每个实例均为全局唯一:
public static final Color RED = new Color();
public static final Color GREEN = new Color();
public static final Color BLUE = new Color();
// private构造方法,确保外部无法调用new操作符:
private Color() {}
}

比较:== (不像其他引用类型只能用 equals()

常量名:String s = Weekday.SUN.name(); // "SUN"

常量顺序:int n = Weekday.MON.ordinal(); // 1 (从0开始)

添加字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum Weekday {
MON(1, "星期一"), TUE(2, "星期二"), WED(3, "星期三"), THU(4, "星期四"), FRI(5, "星期五"), SAT(6, "星期六"), SUN(0, "星期日");

public final int dayValue;
private final String chinese;

private Weekday(int dayValue, String chinese) {
this.dayValue = dayValue;
this.chinese = chinese;
}

@Override
public String toString() {
return this.chinese;
}
}

记录类

record:

  • final 修饰class以及每个字段(实现不变类)
  • 自动创建构造方法,重写 toString()equals()hashCode()
  • 不能直接从 Record 派生,只能通过关键词由编译器实现继承
1
record Point(int x, int y) {...}

相当于:

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
final class Point extends Record {
private final int x;
private final int y;

public Point(int x, int y) {
this.x = x;
this.y = y;
}

public int x() {
return this.x;
}

public int y() {
return this.y;
}

public String toString() {
return String.format("Point[x=%s, y=%s]", x, y);
}

public boolean equals(Object o) {
...
}
public int hashCode() {
...
}
}