面向对象

Java是一门面向对象设计的语言,对 Java语言来说,一切皆是对象

面向过程思想

什么是过程?

过程是指一系列的操作步骤或算法,每个步骤按照特定的顺序执行,直至达到预期的结果。比如现在要制作一杯咖啡,可以将制作咖啡的过程进行分解

  1. 研磨咖啡豆:将咖啡豆研磨成粉末状。
  2. 加水:将适量的水倒入咖啡壶中。
  3. 冲泡咖啡:将咖啡粉末放入滤网中,然后将滤网放入咖啡壶中,倒入热水冲泡。
  4. 倒出咖啡:将冲泡好的咖啡倒入杯子中。
  5. 加调料:根据个人口味加入糖、牛奶等调料。

什么是面向过程?

面向过程是一种基于功能的编程思想,强调了解决问题的步骤和流程,以过程/函数为最小单位,考虑怎么做

对于一个问题,在面向过程编程中,问题会被划分为多个步骤或函数,每个步骤或函数负责完成特定的功能,函数之间通过参数传递数据进行交互,通过调用函数来实现功能。这种方式更注重解决问题的步骤和流程,将问题划分为多个子任务,并按照一定的顺序执行这些子任务来解决问题。

假如现在有一个问题:如何把大象塞进冰箱?以面向过程的思想解决问题,可能需要以下的步骤:

  • 第一步:打开冰箱门。
  • 第二步:把大象放进冰箱。
  • 第三步:关闭冰箱门。

面向对象思想

什么是对象?

对象是对现实世界中具体事物或概念的抽象,每个对象都有自身的特征行为,现实世界中每一个具体事物或概念都可以看做是一个对象

  • 比如现实世界中的一只或者一只,他们都可以看做是一个对象
  • 猫或者狗一般都具有颜色、体重等等特征和吃饭、睡觉等行为
  • 把现实世界中的对象抽象地体现在编程世界中,可以定义类来描述对象,定义属性描述特征,定义方法描述行为

什么是面向对象?

面向对象是一种编程思想,强调了具备功能的对象,以类/对象为最小单位,考虑谁来做

对于一个问题,在面向对象编程中,问题会被抽象为一组相互关联的功能对象,每个对象具有属性和方法。对象之间通过消息传递进行交互,通过调用对象的方法来实现功能。这种方式更注重问题领域中的对象和它们之间的关系,对象拥有自己的状态和行为,通过协同工作来解决问题。

假如现在有一个问题:如何把大象塞进冰箱?以面向对象的思想解决问题可能需要以下的步骤:

  1. 第一步:创建一个冰箱对象。冰箱对象具有方法:打开门()关闭门()
  2. 第二步:创建一个大象对象。大象对象具有方法:移动()
  3. 第三步:调用冰箱对象的打开门()方法。
  4. 第四步:调用大象对象的移动()方法,使其进入冰箱。
  5. 第五步:调用冰箱对象的关闭门()方法。

面向对象与面向过程对比

面向过程性能比面向对象高,因为面向对象中类调用时需要实例化,开销比较大,比较消耗资源;

对比面向对象程序设计面向过程程序设计(也叫结构化编程)
设计语言Java、C++、C#、Python等C、Fortran
特点封装、继承、多态算法+数据结构
优势适用于大型复杂系统,方便复用适用于简单系统,容易理解
劣势比较抽象、性能比面向过程低难以应对复杂系统,难以复用,不易维护、不易扩展

类及类的成员

什么是类

类是对一类事物的描述,描述了一组具有相同特性(属性)和相同行为(方法)的集合,是构造面向对象程序的基本单位

怎么创建类

创建一个类,需要使用 class 关键字,语法格式如下(中括号[]中的部分可省略)

1
2
3
4
// 语法格式
[访问修饰符] class 类名 [extends 父类名] [implements 接口名]{
类的成员(属性、方法、构造器、代码块、内部类)
}
字段概括
访问修饰符对访问权限进行限定的 class的权限修饰只可以用public公共和default缺省
类名首字母大写且符合标识符
extends用于类的继承
implements用于类实现接口
类的成员属性、方法、构造器、代码块、内部类

代码示例

1
2
3
public class User {

}

类的成员

属性(field)

类中属性的定义

1
2
3
4
// 语法格式
[访问修饰符] 数据类型 属性名 = 初始化值;
// 代码示例
public int num = 3;
字段概括
访问修饰符对访问权限进行限定,常用的权限修饰符有:private、缺省、protected、public 其他修饰符:static、final
数据类型任何基本数据类型(如int、boolean) 或 任何引用数据类型
属性名属于标识符,符合命名规则和规范即可

成员变量和局部变量

根据类位置的不同可以将属性划分为成员变量局部变量

属性简介
成员变量在方法体外,类体内声明的变量
局部变量在方法体内部声明的变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Example {
// 成员变量
private int memberVariable;

// 成员方法
public void exampleMethod() {
// 局部变量
int localVariable = 10;

System.out.println("成员变量:" + memberVariable);
System.out.println("局部变量:" + localVariable);
}

public static void main(String[] args) {
Example example = new Example();
example.exampleMethod();
}
}

成员变量和局部变量的区别

区别成员变量局部变量
声明的位置直接声明在类中方法形参或内部、代码块内、构造器内等
访问修饰符private、public、static、final等不能用权限修饰符修饰,可以用final修饰
初始化值有默认初始化值没有默认初始化值,必须显式赋值,方可使用
内存加载位置堆空间 或 静态域内栈空间

方法(method)

类中方法的定义

1
2
3
4
5
// 语法格式
[访问修饰符] 返回值类型 方法名(参数类型 形参1, 参数类型 形参2, …) {
  方法体
  return 返回值;
}
字段概括
修饰符对访问权限进行限定,常用的权限修饰符有:private、缺省、protected、public
返回值类型用于限定方法返回值的数据类型,与方法体中 return 返回值 搭配使用,若没有返回值使用void
方法名属于标识符,命名时遵循标识符命名规则和规范
参数类型限定调用方法时传入参数的数据类型
形参是一个变量,用于接收调用方法时传入的数据
return 返回值方法在执行完毕后返还给调用它的程序的数据。当方法的返回值类型为void时,return及其返回值可以省略

代码示例

1
2
3
4
5
6
public class Demo {
// test方法
public void test(){

}
}

方法的重载(Overload)

在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可

1
2
3
4
5
6
7
8
9
10
public class Demo{
// 方法A
public void A(){

}
// 方法A的重载
public void A(String name){

}
}

方法的重写(Override)

子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作。注意:静态方法无法被子类重写

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 Demo {
public static void main(String[] args) {
Animal animal = new Cat();
animal.hello();
}
}

// 父类Animal
class Animal {
// 父类中的方法
public void hello() {
System.out.println("hello");
}
}

// 子类Cat
class Cat extends Animal {
// @Override注解用来帮助检查方法的正确性
@Override
public void hello() {
System.out.println("重写了父类方法");
}
}

方法的递归(recursion)

(1)递归方法:一个方法体内调用它自身称为递归方法

(2)方法递归包含了一种隐式的循环,会重复执行某段代码,但这种重复执行无须循环控制

(3)递归一定要向已知方向递归(有结束条件),否则这种递归就变成了无穷递归,类似于死循环,导致递归很耗费栈内存,从而发生栈内存溢出错误,递归算法可以不用的时候尽量别用

1
2
3
4
5
6
7
8
9
10
public class Demo {
public static void main(String[] args) {
test();
}
// 递归调用未写结束条件,会报错java.lang.StackOverflowError栈内存溢出错误
public static void test(){
System.out.println("方法的递归调用");
test();
}
}

(4)递归案例:使用递归求1-n的和

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Demo {
public static void main(String[] args) {
System.out.println(sum(100));// 5050
}
// 使用递归求1-n的和
public static int sum(int n) {
if (n == 1) {
return 1;
} else {
return n + sum(n - 1);
}
}
}

构造器(构造方法)

构造器简介

构造方法(Constructor)是一种特殊类型的方法,主要用于创建和初始化对象,具有以下特性:

  • 构造方法名字与类名相同:构造方法的名称必须与类名完全相同
  • 构造方法没有返回值:构造方法没有返回类型,包括 void,也不需要使用 return 关键字。
  • 默认构造方法:如果一个类没有显式定义任何构造方法,编译器会自动提供一个默认的无参构造方法,它什么也不做。默认构造方法的访问修饰符通常为 public
  • 构造方法的重载:可以根据需要在类中定义多个构造方法,可以有不同的参数列表(参数的个数、顺序、类型不同),称为构造方法的重载。每个构造方法可以根据参数的不同来完成不同的初始化任务
  • 构造方法自动执行:当使用 new 关键字创建对象时,构造方法会自动执行,用于初始化新创建的对象,只会执行一次
1
2
3
4
5
6
7
8
// 有参构造器
[访问修饰符] 类名(参数类型 形参1, 参数类型 形参2, …) {
初始化语句;
}
// 无参构造器
public 类名() {

}
字段概括
访问修饰符修饰符只有权限修饰符private、默认、protected、public
不能被static、final、synchronized、abstract、native修饰
类名构造器名称与类名一致
初始化语句用于初始化类的信息 ,不能有 return 语句返回值

构造器代码示例

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

// 无参构造方法
public Person() {

}

// 有参构造方法
public Person(int id, String name) {
this.id = id;// 构造器中调用属性
this.name = name;// 构造器中调用属性
}

// 构造方法的重载
public Person(String name, int age) {
this.name = name;// 构造器中调用属性
this.age = age;// 构造器中调用属性
}
}

代码块(初始化块)

类中代码块的定义

使用{}括起来的代码被称为代码块,常用来初始化类、对象

1
2
3
[访问修饰符] {
初始化语句;
}
字段概括
访问修饰符修饰符可选但只能使用static
初始化语句代码块中可以写任何语句(输入,输出,方法调用,循环,判断等等)

静态代码块

使用static关键字修饰的称为静态代码块,其余的称为非静态代码块

1
2
3
static {
初始化语句;
}

静态代码块与非静态代码块的区别

区别静态代码块非静态代码块
执行时机随着类的加载而执行,而且只执行一次随着对象的创建而执行,每创建一个对象,就执行一次非静态代码块
作用初始化类的信息可以在创建对象时,对对象的属性等进行初始化
调用只能调用静态的属性、方法,不能调用非静态的结构可以调用静态的属性、静态的方法,非静态的属性、非静态的方法

内部类(inner class)

类中内部类的定义

在类中声明的类称为内部类

1
2
3
4
5
6
7
8
9
10
11
// 类A为类B的外部类(outer class)
[访问修饰符] class 类A{
// 类B为类A的内部类(inner class)
[访问修饰符] class 类B{

}
}
// 类C是类A的外部其他类(other class)
class 类C{

}

内部的分类

内部类简介
成员内部类定义在类内部的非静态类叫做成员内部类(不使用static修饰)
静态内部类定义在类内部的静态类称为静态内部类(使用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
public class A {
// 成员内部类
public class B {

}

// 静态内部类
public static class C {

}

public void m() {
// 局部内部类
class C {

}
}

public static void main(String[] args) {
// 匿名内部类
new Test() {

};
}
}

class Test {

}

类的实例化(创建对象)

对象的简介

(1)Java中常说的创建对象就是对类进行实例化,一般会使用 new 关键字创建对象(对类进行实例化)

(2)每个对象都是相互独立的,在内存中占有独立的内存地址,并且每个对象都具有自己的生命周期

(2)Java对象的生命周期包括创建、使用和清除,当对象的生命周期结束时,对象就变成了垃圾,由 Java 虚拟机自带的垃圾回收机制处理

(3)对象具有状态和行为,变量用来表明对象的状态,方法表明对象所具有的行为。

对象的创建(实例化)

1
类名 对象名 = new 类名();

对象的使用

使用普通方式创建对象,访问对象中的成员(属性和方法)

1
2
3
4
// 创建对象
类名 对象名 = new 类名();
// 使用对象名访问对象成员(包括属性和方法)
对象名.对象成员;

使用匿名对象方式(不定义对象的句柄,直接调用对象成员)

1
new 类名().对象成员;

使用案例

(1)先创建一个Person类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Person {

// 属性
String name;
// 属性
int age;

//方法
public void eat() {
System.out.println("人可以吃饭");
}

//方法
public void sleep() {
System.out.println("人可以睡觉");
}

}

(2)创建对象,并访问成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Demo {

public static void main(String[] args) {
// 使用 new 关键字创建对象
Person person = new Person();
// 打印对象地址
System.out.println(person);

// 使用普通方式调用成员(属性和方法)
person.age = 1;// 调用成员变量
person.name = "小明";// 调用成员变量
person.eat();// 调用成员方法
person.sleep();// 调用成员方法

// 使用匿名对象方式调用成员(属性和方法)
new Person().age = 1;// 调用成员变量
new Person().name = "小明";// 调用成员变量
new Person().eat();// 调用成员方法
new Person().sleep();// 调用成员方法
}

}

面向对象的三大特征

特征简介
封装(Encapsulation)封装是面向对象的核心思想,将对象的属性和行为封装起来,不需要让外界知道具体实现细节
继承(Inheritance)继承主要描述的就是类与类之间的关系,通过继承,可以在无需重新编写原有类的情况下,对原有类的功能进行扩展
多态(Polymorphism)多态指的是在一个类中定义的属性和功能被其他类继承后,当把子类对象直接赋值给父类引用变量时,相同引用类型的变量调用同一个方法所呈现出的多种不同行为特性

封装性(Encapsulation)

封装简介

封装可以隐藏对象的实现细节,仅对外公开接口,增强了代码的安全性

封装体现在权限修饰符上:private(类访问级别)、default(包访问级别)、protected(子类访问级别)、public(公共访问级别)

四种权限修饰符

Java权限修饰符publicprotecteddefaultprivate置于类的成员定义前, 用来限定对象对该类成员的访问权限。

修饰符概括
private当前类访问级别,如果类的成员被private访问控制符来修饰
这个成员只能被该类的其他成员访问,其他类无法直接访问 类的良好封装就是通过private关键字来实现的
default包访问级别,如果一个类或者类的成员不使用任何访问控制符修饰,则称它为默认访问控制级别
这个类或者类的成员只能被本包中的其他类访问
protected子类访问级别,如果一个类的成员被protected访问控制符修饰
这个成员既能被同一包下的其他类访问,也能被不同包下该类的子类访问
public公共访问级别,这是一个最宽松的访问控制级别,如果一个类或者类的成员被public访问控制符修饰
这个类或者类的成员能被所有的类访问,不管访问类与被访问类是否在同一个包中

权限修饰符访问级别

修饰符类内部同一个包不同包的子类同一个工程
privateYes
default(缺省)YesYes
protectedYesYesYes
publicYesYesYesYes

继承性(Inheritance)

继承简介

(1)继承是基于已有的类创造新的类,使用继承降低了代码编写的冗余度,便于功能的扩展,提高编程的效率,是多态性的使用的前提

(2)一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法,也可以创建自己的属性和方法

(3)Java 仅支持单继承,一个子类只能有一个父类,一个父类可以有多个子类,一个子类的父类的父类称为间接父类

(4)若子类重写父类方法,就意味着子类里定义的方法彻底覆盖父类里的同名方法,系统将不可能把父类里的方法转移到子类中

(5)所有的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类,如果我们没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类,这就意味着所有的java类都具有java.lang.Object类中声明的功能方法

继承语法

在 Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的

1
2
3
4
5
6
7
8
// 父类A
[访问修饰符] class 父类A{
类的成员(属性、方法、构造器、代码块、内部类)
}
// 子类B继承父类A
[访问修饰符] class 子类B extends 父类A{
类的成员(属性、方法、构造器、代码块、内部类)
}

重写父类方法

(1)子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作

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 Demo {
public static void main(String[] args) {
Animal animal = new Cat();
animal.hello();
}
}

// 父类Animal
class Animal {
// 父类中的方法
public void hello() {
System.out.println("hello");
}
}

// 子类Cat
class Cat extends Animal {
// @Override注解用来帮助检查方法的正确性
@Override
public void hello() {
System.out.println("重写了父类方法");
}
}

(2)案例分析

1
2
3
4
5
6
7
上面的例子中,`animal`对象被定义为父类`Animal`类型。

在编译期,编译器会检查父类`Animal`类中是否有可访问的`hello()`方法,只要其中包含`hello()`方法,那么就可以编译通过。

在运行期,子类`Cat`对象被`new`出来,并赋值给`animal`变量,这时JVM是明确的知道`animal`变量指向的其实是子类`Cat`对象的引用。

所以当`animal`对象调用`hello()`方法的时候,就会调用子类`Cat`类中定义的`hello()`方法。这就是所谓的动态多态性

(3)重写方法的条件

1
2
3
4
5
6
7
8
不能重写被标示为final的方法
参数列表必须完全与被重写方法的相同
返回类型必须完全与被重写方法的返回类型相同
如果不能继承一个方法,则不能重写这个方法
访问级别的限制性一定不能比被重写方法的强
访问级别的限制性可以比被重写方法的弱
重写方法一定不能抛出新的检查异常或比被重写的方法声明的检查异常更广泛的检查异常
重写的方法能够抛出更少或更有限的异常(也就是说,被重写的方法声明了异常,但重写的方法可以什么也不声明)

多态性(Polymorphism)

多态简介

(1)多态可以理解为可以理解为一个事物的多种形态,在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,

(2)多态例子:打印机可以有黑白打印机与彩色打印机,他们都是打印机,但是打印的效果不同

(3)多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理

(4)多态性是运行时的行为,只适用于方法,不适用于属性

实现多态的条件

条件简介
继承必须要有子类继承父类的继承关系
重写子类对父类中的一些方法进行重写,然后调用方法时就会调用子类重写的方法,而不是原本父类的方法
向上转型父类引用指向子类对象(Animal animal = new Cat();)。
在多态中需要将子类的引用赋给父类对象,只有这样该引用才能做到,既能调用父类的方法,又能调用子类的方法

代码示例

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
public class Demo {
public static void main(String[] args) {
// 对象的多态性:父类的引用指向子类的对象
Animal animal1 = new Cat();
Animal animal2 = new Dog();

// 多态的使用:当调用父类同名同参数的方法时,实际执行的是子类重写父类的方法(虚拟方法调用)
animal1.eat();// 子类1吃饭
animal2.eat();// 子类2吃饭

// 对象的多态性只适用于方法,不适用于属性(静态绑定)
System.out.println(animal1.id); //1001
System.out.println(animal2.id); //1001
}

}

// 父类
class Animal {
int id = 1001;
String name;

// 父类方法
public void eat() {

}
}

// 子类1
class Cat extends Animal {
int id = 1002;

// 重写父类方法
@Override
public void eat() {
System.out.println("子类1吃饭");
}
}

// 子类2
class Dog extends Animal {
int id = 1003;

// 重写父类方法
@Override
public void eat() {
System.out.println("子类2吃饭");
}
}

静态绑定和动态绑定

(1)上面例子中等号左边的内容表示父类的引用,该引用在栈内存中,指向了一块堆中的地址,而这个地址中存放的是其子类的对象。animal是Animal类型的一个引用,指向的是其子类Cat的对象,这个就叫做父类引用指向子类对象。程序在编译(javac)的时候animal被看做Animal类型,所以animal.eat()绑定的是Animal类中的eat()方法,这叫做静态绑定,程序运行时,会在堆中开辟空间创建出对象,此时animal指向的是堆中的Cat对象,而在Cat中对eat()方法进行了重写,所以在运行阶段绑定的是Cat中的eat()方法,这叫做动态绑定

(2)对面向对象来说,多态分为编译时多态和运行时多态。

多态简介
编译时多态静态的,主要是指方法的重载,是根据参数列表的不同来区分不同的方法。
通过编译之后会变成两个不同的方法,在运行时谈不上多态
运行时多态动态的,是通过动态绑定来实现的,也就是大家通常所说的多态性

(3)有了对象的多态性以后,在编译期只能调用父类中声明的方法,但在运行期间实际执行的是子类重写父类的方法,提高了代码的通用性,常称作接口重用(编译,看左边,运行,看右边

(4)代码示例

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 Demo {
public static void main(String[] args) {
A b = new B();
// 编译时,只能调用父类中声明的属性和方法, 子类特有的属性和方法不能调用
// b.name; //静态绑定
// b.test(); //静态绑定
b.eat(); //动态绑定,会打印子类重写父类方法

//如何调用子类特有的属性和方法?
//向下转型,使用强制类型转换符(可能出现ClassCastException的异常)
B b1 = (B) b;
b1.test(); // 会打印子类特有的方法
}
}

// 父类A
class A {
String name = "父类属性";

public void eat() {
System.out.println("父类方法");
}
}

// 子类B
class B extends A {
String name = "子类属性";

@Override
public void eat() {
System.out.println("子类重写父类方法");
}

public void test() {
System.out.println("子类特有的方法");
}
}

类型转换

类型转换简介
自动类型转换小的数据类型可以自动转换成大的数据类型
强制类型转换可以把大的数据类型强制转换成小的数据类型,使用强制转换时,可能出现类型转换异常(ClassCastException)
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 Demo {
public static void main(String[] args) {
/**
* 有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,
* 但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法,子类特有的属性和方法不能调用
* */
Animal cat = new Cat();
// cat.sleep(); // 不能调用子类中特有的方法、属性,编译时,cat是父类Animal类型

// 调用子类特有的属性和方法需要向下转型,使用强制类型转换符()
Cat cat1 = (Cat) cat;
cat1.sleep(); // 子类睡觉
}
}

// 父类
class Animal {
int id = 1001;
String name;

// 父类方法
public void eat() {

}
}

// 子类
class Cat extends Animal {
int id = 1002;

// 重写父类方法
@Override
public void eat() {
System.out.println("子类吃饭");
}

public void sleep() {
System.out.println("子类睡觉");
}
}

instanceof关键字

会判断引用指向的对象是否是该类型的,如果是则计算结果是true,否则结果是false

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
/**
* instanceof关键字的使用方式:a instanceof A
* 判断对象a是否是类A的实例,如果是,返回true:如果不是,返回false
* 使用情境:为了避免在向下转型时出现ClassCastException的异常,在向下转型之前,先进行instanceof的判断,
* 如果返回true就进行向下转型,如果返回false,不进行向下转型
*/
public class Demo {
public static void main(String[] args) {
Animal cat = new Cat();
Animal dog = new Dog();

// 如果cat是Cat类的实例,就执行强制类型转换
if (cat instanceof Cat) {
Cat c = (Cat) cat; // 强制类型转换
c.sleep(); // 调用子类特有方法
}
// 如果cat是Dog类的实例,就执行强制类型转换
if (cat instanceof Dog) {
Cat c = (Cat) cat; // 强制类型转换
c.sleep(); // 调用子类特有方法
}
}
}

// 父类
class Animal {
int id = 1001;
String name;

// 父类方法
public void eat() {

}
}

// 子类1
class Cat extends Animal {
int id = 1002;

// 重写父类方法
@Override
public void eat() {
System.out.println("子类1吃饭");
}

// 子类1特有方法
public void sleep() {
System.out.println("子类1睡觉");
}
}

// 子类2
class Dog extends Animal {
int id = 1003;

// 重写父类方法
@Override
public void eat() {
System.out.println("子类2吃饭");
}

// 子类2特有方法
public void sleep() {
System.out.println("子类2睡觉");
}
}

抽象类与接口

抽象类

定义方式

使用abstract关键字修饰的类称为抽象类,抽象类不能实例化(不能造对象)

1
abstract class Test{…}

成员变量

抽象类可以定义各种类型的成员变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
abstract class AbstractClass {
// 基本数据类型成员变量
private int intValue;

// 引用类型成员变量
private String stringValue;

// 静态变量
private static boolean booleanValue;

// 静态常量
private static final double PI = 3.14159;
}

构造函数

抽象类中一定有构造器,便于子类对象实例化时调用,开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
abstract class AbstractClass {
// 基本数据类型成员变量
private int intValue;

// 引用类型成员变量
private String stringValue;

// 构造方法
public AbstractClass(int intValue, String stringValue) {
this.intValue = intValue;
this.stringValue = stringValue;
}
}

代码块

抽象类中可以使用代码块

1
2
3
4
5
6
7
8
9
10
11
abstract class AbstractClass {
// 普通代码块
{
System.out.println("普通代码块");
}

// 静态代码块
static {
System.out.println("静态代码块");
}
}

方法

抽象类可以包含抽象方法和普通方法,包含抽象方法的类,一定是一个抽象类,抽象类中可以没有抽象方法

  • 抽象方法:使用abstract关键字修饰的方法称为抽象方法,抽象方法只有方法的声明,没有方法体

  • 普通方法:有方法的声明,也有方法体

1
2
3
4
5
6
7
8
9
10
abstract class AbstractClass {
// 抽象方法
修饰符 abstract 返回值类型 方法名(参数列表);

// 普通方法
[访问修饰符] 返回值类型 方法名(参数列表) {
方法体
return 返回值;
}
}

单继承

一个类只能继承一个类,抽象类中的子类可以是抽象类,如果不是抽象类的话必须对抽象类中的抽象方法进行重写

  • 抽象子类:若子类没有重写父类中的所有抽象方法,此子类也是一个抽象类,此子类不能实例化,需要用abstract修饰
  • 普通子类:若子类重写了父类中的所有的抽象方法后,此子类是一个普通类,此子类可实例化
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
abstract class AbstractClass {
// 构造方法
public AbstractClass(int intValue, String stringValue) {
this.intValue = intValue;
this.stringValue = stringValue;
}

// 抽象方法
public abstract void abstractMethod();

// 非抽象方法
public void concreteMethod() {
System.out.println("这是一个具体的方法。");
}

}

// 普通子类:继承抽象类,并实现所有抽象方法,此子类可实例化
class ConcreteClass extends AbstractClass {
// 普通子类构造方法
public ConcreteClass(int intValue, String stringValue) {
super(intValue, stringValue);
}

// 子类实现父类的抽象方法
@Override
public void abstractMethod() {
System.out.println("抽象方法实现。");
}
}

// 抽象子类:继承抽象类,不实现抽象方法,此子类不可实例化
abstract class AbstractSubclass extends AbstractClass {
// 抽象子类构造方法
public AbstractSubclass(int intValue, String stringValue) {
super(intValue, stringValue);
}
}

完整代码示例

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
abstract class AbstractClass {
// 基本数据类型成员变量
private int intValue;

// 引用类型成员变量
private String stringValue;

// 静态变量
private static boolean booleanValue;

// 静态常量
private static final double PI = 3.14159;

// 构造方法
public AbstractClass(int intValue, String stringValue) {
this.intValue = intValue;
this.stringValue = stringValue;
}

// 抽象方法
public abstract void abstractMethod();

// 非抽象方法
public void concreteMethod() {
System.out.println("这是一个具体的方法。");
}

// 普通代码块
{
System.out.println("普通代码块");
}

// 静态代码块
static {
System.out.println("静态代码块");
}
}

// 普通子类:继承抽象类,并实现所有抽象方法,此子类可实例化
class ConcreteClass extends AbstractClass {
// 普通子类构造方法
public ConcreteClass(int intValue, String stringValue) {
super(intValue, stringValue);
}

// 子类实现父类的抽象方法
@Override
public void abstractMethod() {
System.out.println("抽象方法实现。");
}
}

// 抽象子类:继承抽象类,不实现抽象方法,此子类不可实例化
abstract class AbstractSubclass extends AbstractClass {
// 抽象子类构造方法
public AbstractSubclass(int intValue, String stringValue) {
super(intValue, stringValue);
}
}

接口

定义方式

使用interface来声明一个接口,接口其实是一个特殊的抽象类

1
interface Test{...}

成员变量

接口中的变量前面会被默认加上public static final的,即接口中没有变量,都是常量

1
2
3
4
5
6
7
8
// 定义接口
interface A {
// 基本数据类型静态常量,等同于 public static final int MAX_SPEED = 60;
int MAX_SPEED = 60;

// 引用类型静态常量,等同于 public static final String TYPE = "Land";
String TYPE = "Land";
}

构造函数

在Java中,接口是一种定义方法的规范,它不能包含构造函数

代码块

接口中不能使用代码块

方法

接口可以包含抽象方法、默认方法和静态方法

抽象方法:接口中的方法会被默认加上abstract关键字,使用abstract关键字修饰的方法称为抽象方法,抽象方法只有方法的声明,没有方法体

默认方法:使用default关键字修饰,有方法的声明,也有方法体

静态方法:使用static关键字修饰,有方法的声明,也有方法体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface A {    
// 抽象方法,等同于 abstract String sound();
void methodA();

// 默认方法
default void defaultMethod() {
System.out.println("接口A中的默认方法");
}

// 静态方法
static void staticMethod() {
System.out.println("接口A中的静态方法");
}
}

多实现

一个类可以实现多个接口,通过逗号分隔。当实现了接口之后,需要满足以下要求

  • 必须重写抽象方法:必须在实现类中重写所有抽象方法,否则该类必须使用abstract关键字声明为抽象类。
  • 选择性重写默认方法:可以选择性地进行重写,如果不重写,实现类将继承接口中的默认实现。
  • 不能重写静态方法:无法被子类重写或覆盖,可以直接通过接口名调用。
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
interface A {
// 抽象方法,等同于 abstract String sound();
void methodA();

// 默认方法
default void defaultMethod() {
System.out.println("接口A中的默认方法");
}

// 静态方法
static void staticMethod() {
System.out.println("接口A中的静态方法");
}
}

interface B {
// 抽象方法,等同于 abstract String sound();
void methodB();

// 默认方法
default void defaultMethod() {
System.out.println("接口B中的默认方法");
}

// 静态方法
static void staticMethod() {
System.out.println("接口B中的默认方法");
}
}

// 实现一个接口,只重写抽象方法
class MyClassC implements A {
@Override
public void methodA() {
System.out.println("重写方法A");
}
}

// 实现两个接口,重写抽象方法、默认方法
class MyClassD implements A, B {
@Override
public void methodA() {
System.out.println("重写方法A");
}

@Override
public void methodB() {
System.out.println("重写方法B");
}

// 选择重写默认方法
@Override
public void defaultMethod() {
System.out.println("重写默认方法");
}
}

// 实现两个接口,不重写抽象方法
abstract class MyClassE implements A, B {
// 接口 A 和 B 都包含 defaultMethod 同名默认方法。如果不重写,编译器将会报错
@Override
public void defaultMethod() {
System.out.println("重写默认方法");
}
}

完整代码示例

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
71
72
73
74
75
76
77
interface A {
// 基本数据类型静态常量,等同于 public static final int MAX_SPEED = 60;
int MAX_SPEED = 60;

// 引用类型静态常量,等同于 public static final String TYPE = "Land";
String TYPE = "Land";

// 抽象方法,等同于 abstract String sound();
void methodA();

// 默认方法
default void defaultMethod() {
System.out.println("接口A中的默认方法");
}

// 静态方法
static void staticMethod() {
System.out.println("接口A中的静态方法");
}
}

interface B {
// 基本数据类型静态常量,等同于 public static final int MAX_SPEED = 60;
int MAX_SPEED = 60;

// 引用类型静态常量,等同于 public static final String TYPE = "Land";
String TYPE = "Land";

// 抽象方法,等同于 abstract String sound();
void methodB();

// 默认方法
default void defaultMethod() {
System.out.println("接口B中的默认方法");
}

// 静态方法
static void staticMethod() {
System.out.println("接口B中的默认方法");
}
}

// 实现一个接口,只重写抽象方法
class MyClassC implements A {
@Override
public void methodA() {
System.out.println("重写方法A");
}
}

// 实现两个接口,重写抽象方法、默认方法
class MyClassD implements A, B {
@Override
public void methodA() {
System.out.println("重写方法A");
}

@Override
public void methodB() {
System.out.println("重写方法B");
}

// 选择重写默认方法
@Override
public void defaultMethod() {
System.out.println("重写默认方法");
}
}

// 实现两个接口,不重写抽象方法
abstract class MyClassE implements A, B {
// 接口 A 和 B 都包含 defaultMethod 同名默认方法。如果不重写,编译器将会报错
@Override
public void defaultMethod() {
System.out.println("重写默认方法");
}
}

抽象类与接口的区别

  1. 定义方式:抽象类使用abstract关键字来修饰,接口使用interface关键字来修饰。
  2. 构造函数:抽象类可以有构造函数,而接口不能有构造函数。
  3. 代码块:抽象类中可以包含代码块,而接口中不能有代码块。
  4. 成员变量:抽象类可以定义各种类型的成员变量,而接口中的变量默认为public static final型常量。
  5. 方法:抽象类可以包含非抽象方法和抽象方法,而接口只能包含抽象方法、默认方法和静态方法。
  6. 单继承与多实现:一个类可以继承一个抽象类,但只能实现多个接口。
  7. 主要作用:抽象类用于作为其他类的基类,定义了一些通用行为和属性,子类可以继承这些通用行为和属性,并在需要的基础上进行扩展或修改。接口用于定义一组规范,描述了一个对象应该具备的行为

常见关键字

this关键字

(1)this表示当前对象的引用,可以使用this关键字访问当前对象的成员变量、成员方法和构造方法。

1
2
3
4
5
6
// 调用当前对象的构造器,需要在构造器中使用,必须放在构造器的首行
this(形参列表);
// 调用当前对象的属性
this.属性;
// 调用当前对象的方法
this.方法;

(2)当类中成员变量与构造方法形参重名时,可以使用this区分同名变量

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 Demo {
public String name;
public int age;

// 空参构造
public Demo() {
// 构造器中调用构造器,必须放在构造器的首行
this("小白", 18);
// 调用当前对象的方法
this.hello();
}

// 全参构造
public Demo(String name, int age) {
// 构造器中调用属性
this.name = name;
this.age = age;
}

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

(3)当需要返回当前对象的引用时,就常常在方法写return this

1
2
3
4
5
6
public class Demo{
public Demo test(){
// 返回类的当前对象
return this;
}
}

super关键字

(1)super表示当前对象的父类(超类)的引用,可以使用super关键字访问父类的成员变量、成员方法和构造方法

1
2
3
4
5
6
// 调用父类对象的构造器,需要在构造器中使用,必须放在构造器的首行
super(参数列表)
// 调用父类的属性
super.属性
// 调用父类的方法
super.方法

(2)super关键字可用于访问父类中定义的属性、方法,在子类构造器中调用父类的构造器

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
// 父类Animal
class Animal {

public String name;

// 构造函数
public Animal(String name) {
// 构造器中调用属性
this.name = name;
}
// 父类中的方法
public void hello() {
System.out.println("hello");
}
}

// 子类Cat类
class Cat extends Animal {
//构造函数
public Cat() {
// 调用父类对象的构造器,需要在构造器中使用,必须放在构造器的首行
super("喵喵");
// 调用父类的属性
super.name = "绵绵";
// 调用父类的hello()方法
super.hello();
}
}

(3)this和super的区别

区别点thissuper
访问属性访问本类中的属性,如果本类没 有此属性则从父类中继续查找直接访问父类中的属性
调用方法访问本类中的方法,如果本类没 有此方法则从父类中继续查找直接访问父类中的方法
调用构造器调用本类构造器,必须放在构造器的首行调用父类构造器,必须放在子类构造器的首行

abstract关键字(抽象)

abstract关键字可以用来修饰的类(抽象类)、方法(抽象方法)

abstract关键字不能用来修饰属性、私有方法、静态方法、构造方法、final修饰的方法、final修饰的类

interface关键字(接口)

interface关键字用于定义接口

package关键字(包)

包概念相关

(1)为了更好地对类进行统一的管理和划分,Java提供了包机制。使用package关键字来声明类或接口所属的包,声明在源文件的首行

(2)包的作用:解决类命名冲突的问题、控制访问权限、划分项目层次,便于管理

(3)如果在源文件中没有定义包,那么类、接口、枚举和注释类型文件将会被放进一个无名的包中,也称为默认包。在实际企业开发中,通常不会把类定义在默认包下

语法格式

(1)包声明在源文件的首行,语法格式如下

1
2
// 声明在源文件的首行
package pkg1[.pkg2[.pkg3…]];

(2)Java 包的命名规则

1
2
3
4
1、包名全部由小写字母(多个单词也全部小写)
2、如果包名包含多个层次,每个层次用“.”分割
3、包名一般由倒置的域名开头,比如 com.baidu,不要有 www
4、自定义包不能 java 开头

包导入(import)

(1)如果使用不同包中的其它类,需要使用该类的全名(包名+类名)

1
2
// com.wen为包名,Test为包下的类
com.wen.Test() test = new com.wen.Test();

(2)为了简化编程,Java 引入了 import 关键字,用于导入指定包层次下的某个类或全部类

注意:使用星号(*)可能会增加编译时间,特别是引入多个大包时,所以明确的导入你想要用到的类是一个好方法,需要注意的是使用星号对运行时间和类的大小没有影响

1
2
3
4
// 导入某个包下的某个类
import 包名.类名;
// 导入某个包下的所有类
import 包名.*;

(3)import 语句位于 package 语句之后,类定义之前。一个 Java 源文件只能包含一个 package 语句,但可以包含多个 import 语句

(4)Java 默认为所有源文件导入 java.lang 包下的所有类,因此前面在 Java 程序中使用 String、System 类时都无须使用 import 语句来导入这些类

系统包

Java SE 提供了一些系统包,其中包含了 Java 开发中常用的基础类

简介
java.lang包含一些Java语言的核心类,如String、Math、Integer、 System和 Thread,提供常用功能
java.net包含执行与网络相关的操作的类和接口
java.io包含能提供多种输入/输出功能的类
java.util包含一些实用工具类,如定义系统特性、接口的集合框架类、使用与日期日历相关的函数
java.text包含了一些java格式化相关的类
java.sql包含了Java进行JDBC数据库编程的相关类/接口
java.awt包含了构成抽象窗口工具集的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)

Jar包

(1)Java中的jar包就是java类在编译生成class文件后,进行打包的压缩包,jar包里面就是.class文件

(2)因为jar包主要是对class文件进行打包,而java编译生成的class文件是平台无关的,这就意味着jar包是跨平台的,所以不必关心涉及具体平台的问题

(3)将一个类打为 jar 包的语法

1
2
3
4
// (1)编译
javac -d . 类名.java
// (2)打jar包,生成名叫myjar.jar的文件,参数c参数创建,参数v参数显示创建过程文件的详细信息
jar -cvf myjar.jar cn

(4)查看 jar 文件内容列表

1
2
// 参数t表示显示列表,参数v表示显示文件的详细信息
jar -tvf myjar.jar

(5)解压 jar 文件

1
jar xvf myjar.jar

(6)运行jar文件

1
java -jar myjar.jar

(7)jar是一种基于ZIP压缩格式的文件,除了扩展名和zip不一样之外,可以认为一个jar文件和一个zip文件是完全一样的,因此二者仅仅是后缀名称的区别而已,也就是说将文件压缩为zip格式,通过java -jar也可以运行

static关键字(静态)

static关键字可以用来修饰属性、方法、代码块、内部类。

  • 修饰属性:用static修饰的属性为静态属性,也称为类变量。静态属性属于类本身,可以直接通过类名调用,无需创建类的实例对象。
  • 修饰方法:用static修饰的方法为静态方法,也称为类方法。静态方法属于类本身,可以直接通过类名调用,无需创建类的实例对象。静态方法中``不能直接访问实例方法和实例变量,也不能使用this关键字、super关键字。静态方法不能被重写`
  • 修饰代码块:用static修饰的代码块称为静态代码块。静态代码块在类加载时执行,并且只会执行一次。它可用于进行静态变量的初始化或其他静态操作。
  • 修饰内部类:用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
40
41
42
43
44
45
46
47
48
49
50
51
52
class MyClass {
public static void main(String[] args) {
MyClass.staticVariable = 2;// 通过类名调用静态属性
MyClass.staticMethod();// 通过类名调用静态方法
MyClass.StaticInnerClass.staticInnerMethod();// 通过类名调用静态内部类中的静态方法
}

// 静态属性
public static int staticVariable;
// 实例属性
public int instanceVariable;

// 静态方法
public static void staticMethod() {
// 静态方法不能直接访问实例变量和实例方法
// MyClass.instanceVariable = 2; // 直接访问实例变量会报错
// MyClass.instanceMethod(); // 直接访问实例方法也会报错

// 静态方法不能使用this关键字、super关键字
// System.out.println(MyClass.this.getClass()); // 使用this关键字会报错
// System.out.println(MyClass.super.getClass()); // 使用super关键字也会报错

System.out.println("静态方法被执行");
}

// 实例方法
public void instanceMethod() {
// 实例方法可以直接访问静态变量和调用静态方法
MyClass.staticVariable = 2;
MyClass.staticMethod();
// 也可以使用this关键字、super关键字
System.out.println(MyClass.this.getClass());
System.out.println(MyClass.super.getClass());

}

// 静态代码块,类加载时执行,仅执行一次
static {
// 可用于进行静态变量的初始化或其他静态操作
}

// 静态内部类,与外部类间没有直接的关联,可以直接创建静态内部类的实例对象
public static class StaticInnerClass {
public static void staticInnerMethod() {
System.out.println("静态内部方法被执行");
}

public void instanceInnerMethod() {
System.out.println("实例内部方法被执行");
}
}
}

final关键字(最终)

final关键字可以用来修饰的结构:类、方法、变量(成员变量、局部变量、形参、引用地址)

  • 修饰类:用final修饰的类不能被其他类继承
  • 修饰方法:用final修饰的方法不能被子类重写
  • 修饰变量:用final修饰的变量表示常量,即其值一旦被初始化后就不能被修改。对于基本数据类型的变量,该值是不可变的;对于引用类型的变量,该引用不能再指向其他对象,但是该对象的内容可以被修改。
1
2
3
4
5
6
7
8
9
10
11
public final class MyClass {// 使用final修饰的 类MyClass,不能被继承

final int age = 30; // 使用final修饰的 成员变量age,值不能被修改

// 使用final修饰的 方法myMethod,不能被子类重写
public final void myMethod(final int num) {// 使用final修饰方法的 形参num,值不能被修改
final String message = "Hello"; // 使用final修饰的 局部变量message,值不能被修改
final Object obj = new Object();// 使用final修饰的 引用地址obj,不能再指向其他对象
}

}

transient关键字

在Java中,transient是一个关键字,用来修饰类的成员变量。当一个变量被声明为transient时,它表示该变量不参与对象的序列化过程,将被跳过,不会被序列化,通常用于一些敏感信息或者不需要被序列化和传输的数据

1
2
3
4
public class Person implements Serializable {
private String name;
private transient String password;
}

native关键字

(1)native关键字用于表示该方法是调用其他语言编写的实现。

(2)出现原因:由于某些情况使用Java语言不易编写或效率不高,此时可以使用其他语言实现,然后再通过Java来进行调用

(3)Java源码中有的方法是native修饰的,例如:Object类中的hashCode()方法

1
2
3
4
5
public class Object {
...
public native int hashCode();
...
}

Java栈、堆、方法区

栈(Stack)

栈是一种后进先出(LIFO)的数据结构,用于存储方法调用时的局部变量、基本数据类型和对象的引用。

1
2
3
4
5
6
public class StackExample {
public static void main(String[] args) {
int num = 10; // 基本数据类型,存储在栈区
String str = "Hello"; // 引用类型,存储在栈区
}
}

堆(Heap)

堆用于存储动态分配的对象,所有通过 new 关键字创建的对象都存储在堆中

1
2
3
4
5
6
public class HeapExample {
public static void main(String[] args) {
MyClass obj1 = new MyClass(); // 创建一个对象,存储在堆区
MyClass obj2 = new MyClass(); // 创建另一个对象,存储在堆区
}
}

方法区(Method Area)

方法区用于存储类的信息、常量池、静态变量等数据。

1
2
3
4
5
6
7
8
9
public class MethodAreaExample {
public static final String CONSTANT = "Hello"; // 常量,存储在方法区
public static int count = 0; // 静态变量,存储在方法区

public static void main(String[] args) {
System.out.println(CONSTANT);
System.out.println(count);
}
}

对象实例化的过程

  1. 内存分配:在对象实例化之前,需要为对象分配内存空间。对于全局对象、静态对象和栈区域内的对象,内存分配在编译阶段完成;而对于堆空间中的对象,内存分配是在运行阶段进行的。
  2. 类加载:在对象实例化之前,需要先加载对象所属的类。类的加载包括加载类的字节码文件,并将类信息存储在方法区中。
  3. 初始化:初始化是在对象创建时进行的过程。它发生在赋值之前,主要用来设置对象的初始状态。初始化包括对对象的成员变量进行默认初始化(如整型默认为0,引用类型默认为null)以及执行构造函数中的初始化代码。
  4. 执行构造方法:构造方法是一种特殊的方法,用于创建和初始化对象。在对象创建后,会自动调用相应的构造方法。构造方法中可以进行一些额外的初始化操作,如给对象的成员变量赋值、执行特定的逻辑等。
  5. 赋值:赋值是在对象初始化完成后,为对象的成员变量设置具体的值。赋值过程可以在构造方法的函数体中进行。在赋值过程中,可以使用构造方法的参数或其他逻辑进行赋值操作,将对象的成员变量设置为所需的值。
  6. 返回对象引用:在对象初始化完成后,可以通过返回对象引用来使用和操作该对象。

对象实例化的例子

  1. 程序从main方法开始执行,将main方法压栈。
  2. 在执行到Student student = new Student("Tom", 18);这行代码时,会在堆内存中分配一块空间存储创建的Student对象。
  3. 假设分配的内存地址是0x0101,那么变量student会在栈内存中被创建并指向这个内存地址。
  4. 执行Student类的构造方法public Student(String name, int age),在构造方法中将参数值赋给对象的成员变量nameage
  5. 完成对象的初始化后,student引用指向了实际的对象。
  6. 执行student.displayInfo()方法,调用Student类中的displayInfo()方法,显示学生的信息。
  7. 执行完该方法内的代码后,将该方法从栈中弹出(即方法出栈)。
  8. 如果还有其他方法调用或代码要执行,继续执行相应的代码块,直到主程序执行完毕。
  9. 程序执行结束,程序退出。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Student {
private String name;
private int age;

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

public void displayInfo() {
System.out.println("Name: " + name);
System.out.println("Age: " + age);
}

public static void main(String[] args) {
// 创建Student对象
Student student = new Student("Tom", 18);
// 调用displayInfo()方法显示学生信息
student.displayInfo();
}

}

四种拷贝

引用拷贝

引用拷贝只复制了对象的引用,新旧对象将指向同一块内存地址,修改新对象会影响到原对象,因为它们共享相同的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
class Person {
private String name;
private List<String> hobbies;
}
public class 引用拷贝 {
public static void main(String[] args) {
List<String> hobbies = new ArrayList<>();
hobbies.add("读书");
hobbies.add("游泳");
Person person1 = new Person("小明", hobbies);

Person person2 = person1; // 引用拷贝
person2.setName("小刚");

System.out.println(person1.getName()); // 输出结果:小刚
System.out.println(person2.getName()); // 输出结果:小刚
}
}

对象拷贝

对象拷贝是通过调用对象的拷贝构造函数或克隆方法创建一个新的对象的副本。新对象与原对象是完全独立的,修改新对象不会影响到原对象。

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
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
class Person implements Cloneable {
private String name;
private List<String> hobbies;

@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class 对象拷贝 {
public static void main(String[] args) throws CloneNotSupportedException {
List<String> hobbies = new ArrayList<>();
hobbies.add("读书");
hobbies.add("游泳");
Person person1 = new Person("小明", hobbies);

Person person2 = (Person) person1.clone(); // 对象拷贝
person2.setName("小刚");

System.out.println(person1.getName()); // 输出结果:小明
System.out.println(person2.getName()); // 输出结果:小刚
}
}

浅拷贝

浅拷贝是创建了一个新对象,该对象的非静态成员变量被复制为原始对象的值。对于引用类型的成员变量,浅拷贝只是复制了引用地址,新对象和原对象中的引用类型成员变量仍然指向相同的对象。

  • 非静态成员变量:值会复制给新对象
  • 引用类型成员变量:只是复制引用地址,新对象和原对象中的引用类型成员变量仍然指向相同的对象。
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
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
class Person implements Cloneable {
private String name;
private List<String> hobbies;

// 浅拷贝
public Object shallowCopy() throws CloneNotSupportedException {
return super.clone();
}
}
public class 浅拷贝 {
public static void main(String[] args) throws CloneNotSupportedException {
List<String> hobbies = new ArrayList<>();
hobbies.add("读书");
hobbies.add("游泳");
Person person1 = new Person("小明", hobbies);

Person person2 = (Person) person1.shallowCopy();// 浅拷贝
person2.setName("小刚");

System.out.println(person1);// copy.浅拷贝.Person@7f31245a
System.out.println(person2);// copy.浅拷贝.Person@6d6f6e28
System.out.println(person1 == person2); // 输出 false 说明引用地址不一样

System.out.println(person1.getName()); // 输出 小明
System.out.println(person2.getName()); // 输出 小刚

// 修改person2的hobbies列表
person2.getHobbies().add("跑步");

System.out.println(person1.getHobbies()); // 输出 [读书, 游泳, 跑步]
System.out.println(person2.getHobbies()); // 输出 [读书, 游泳, 跑步]
}
}

深拷贝

深拷贝是在创建新对象时,不仅复制原始对象中的基本类型成员变量的值,还复制引用类型成员变量所引用的对象。换句话说,深拷贝会递归地复制对象及其所有子对象,新对象和原对象完全独立。

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
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
class Person implements Cloneable {
private String name;
private List<String> hobbies;

// 深拷贝
public Person deepCopy() throws CloneNotSupportedException {
List<String> clonedHobbies = new ArrayList<>(this.hobbies);
return new Person(this.name, clonedHobbies);
}
}
public class 深拷贝 {
public static void main(String[] args) throws CloneNotSupportedException {
List<String> hobbies = new ArrayList<>();
hobbies.add("读书");
hobbies.add("游泳");
Person person1 = new Person("小明", hobbies);

Person person2 = person1.deepCopy();// 深拷贝
person2.setName("小刚");

System.out.println(person1);// copy.浅拷贝.Person@7f31245a
System.out.println(person2);// copy.浅拷贝.Person@6d6f6e28
System.out.println(person1 == person2); // 输出 false 说明引用地址不一样

System.out.println(person1.getName()); // 输出 小明
System.out.println(person2.getName()); // 输出 小明

// 修改person2的hobbies列表
person2.getHobbies().add("唱歌");

System.out.println(person1.getHobbies()); // 输出 [读书, 游泳]
System.out.println(person2.getHobbies()); // 输出 [读书, 游泳, 唱歌]
}
}

深拷贝实现方式一:继承Cloneable重写clone方法

Java中的clone()方法支持对象的复制,但默认仅实现了浅拷贝。如果要实现深拷贝,需要将对象实现Cloneable接口,并重写clone()方法。在clone()方法内部,将对象及其内部所有属性全部复制一份即可

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
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
class Person implements Cloneable {
private String name;
private List<String> hobbies;

// 深拷贝
public Person deepCopy() throws CloneNotSupportedException {
List<String> clonedHobbies = new ArrayList<>(this.hobbies);
return new Person(this.name, clonedHobbies);
}
}
public class 深拷贝 {
public static void main(String[] args) throws CloneNotSupportedException {
List<String> hobbies = new ArrayList<>();
hobbies.add("读书");
hobbies.add("游泳");
Person person1 = new Person("小明", hobbies);

Person person2 = person1.deepCopy();// 深拷贝
person2.setName("小刚");

System.out.println(person1);// copy.浅拷贝.Person@7f31245a
System.out.println(person2);// copy.浅拷贝.Person@6d6f6e28
System.out.println(person1 == person2); // 输出 false 说明引用地址不一样

System.out.println(person1.getName()); // 输出 小明
System.out.println(person2.getName()); // 输出 小明

// 修改person2的hobbies列表
person2.getHobbies().add("唱歌");

System.out.println(person1.getHobbies()); // 输出 [读书, 游泳]
System.out.println(person2.getHobbies()); // 输出 [读书, 游泳, 唱歌]
}
}

深拷贝实现方式二:使用序列化

通过将对象序列化为字节流,再将字节流反序列化为新的对象,就可以实现深拷贝。

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
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
class Person implements Serializable {
private String name;
private List<String> hobbies;

// 深拷贝
public Person deepCopy() throws IOException, ClassNotFoundException {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream objectOut = new ObjectOutputStream(byteOut);
objectOut.writeObject(this);

ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream objectIn = new ObjectInputStream(byteIn);
return (Person) objectIn.readObject();
}
}

public class 深拷贝 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
List<String> hobbies = new ArrayList<>();
hobbies.add("读书");
hobbies.add("游泳");
Person person1 = new Person("小明", hobbies);

Person person2 = person1.deepCopy(); // 深拷贝
person2.setName("小刚");

System.out.println(person1); // 输出 copy.深拷贝.Person@7f31245a
System.out.println(person2); // 输出 copy.深拷贝.Person@6d6f6e28
System.out.println(person1 == person2); // 输出 false,说明引用地址不一样

System.out.println(person1.getName()); // 输出 小明
System.out.println(person2.getName()); // 输出 小刚

// 修改person2的hobbies列表
person2.getHobbies().add("唱歌");

System.out.println(person1.getHobbies()); // 输出 [读书, 游泳]
System.out.println(person2.getHobbies()); // 输出 [读书, 游泳, 唱歌]
}
}

深拷贝实现方式三:使用工具

Spring包下的org.springframework.beans.BeanUtils.copyProperties();

1
2
3
public static void copyProperties(Object source, Object target) throws BeansException {
copyProperties(source, target, null, (String[]) null);
}

Apeche包下的org.apache.commons.beanutils.BeanUtils.copyProperties();

1
2
3
public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {
BeanUtilsBean.getInstance().copyProperties(dest, orig);
}

四种引用

强引用(Strong Reference)

强引用是最常见的引用类型,如果一个对象具有强引用,就表示垃圾回收器不会对其进行回收。即使内存空间不足时,垃圾回收器也不会回收被强引用关联的对象。

1
Object obj = new Object(); // 强引用

软引用(Soft Reference)

软引用用于描述还有用但并非必需的对象。在系统内存不足时,垃圾回收器可能会回收软引用关联的对象来释放内存。可以使用SoftReference类来创建软引用。

1
SoftReference<Object> softRef = new SoftReference<>(new Object()); // 软引用

弱引用(Weak Reference)

弱引用用于描述非必需对象。无论系统内存是否充足,只要发生垃圾回收操作,垃圾回收器都会回收掉只被弱引用关联的对象。可以使用WeakReference类来创建弱引用。

1
WeakReference<Object> weakRef = new WeakReference<>(new Object()); // 弱引用

虚引用(Phantom Reference)

虚引用用于管理对象被垃圾回收器回收的时机。虚引用与其他引用类型的主要区别在于,无法通过虚引用访问对象,也无法通过虚引用获取对对象的引用。可以使用PhantomReference类来创建虚引用。

1
PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), referenceQueue); // 虚引用

四种引用的区别

引用类型垃圾回收行为生存时间用途
强引用 (Strong Reference)不会被回收长期存在,直到引用被显式释放主要引用类型,常用于对象的正常引用
软引用 (Soft Reference)在内存不足时可能被回收长期存在,直到内存不足时回收用于缓存或者需要某些操作前的临时引用
弱引用 (Weak Reference)在垃圾回收时可能被回收长期存在,直到没有强引用用于实现缓存、监控和弱关联等功能
虚引用 (Phantom Reference)在垃圾回收时可能被回收一旦被垃圾回收器处理完毕即消失用于对象被回收前的清理工作或跟踪回收状态

JavaBean

JavaBean是一种符合特定规范的Java类,通常用于在应用程序中传递数据或存储数据。

JavaBean的特征

满足以下条件才是JavaBean

条件描述
是一个公共类(public)类声明为公共的,这样才能在其他类中访问和使用它。
具有公共的方法(public)提供公共的getter和setter方法来访问和修改私有属性。
具有私有的属性(private)提供私有属性来封装数据,这些属性应该通过访问器(getter)和修改器(setter)来进行访问和修改。
具有无参的构造方法提供公共的无参构造方法,以便在反射时可以实例化对象
可序列化如果需要将JavaBean的对象进行持久化或通过网络进行传输,JavaBean应该实现Serializable接口。

JavaBean代码示例

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
import java.io.Serializable;

public class Person implements Serializable {
// 属性一般定义为private
private String name;
private int age;

// 无参构造方法
public Person() {

}

// get方法
public String getName() {
return name;
}

// set方法
public void setName(String name) {
this.name = name;
}

// get方法
public int getAge() {
return age;
}

// set方法
public void setAge(int age) {
this.age = age;
}
}

JavaBean的好处

  1. 封装性:JavaBean通过封装数据和提供访问方法来实现数据的封装,使得代码更加模块化、可维护性更高。
  2. 可重用性:JavaBean可以在不同的应用程序或模块中被多次使用,提高代码的可重用性。
  3. 可读性:JavaBean的命名规范和规范化的结构使得代码更加易读和易理解。

枚举类(enum)

枚举类简介

枚举类是一种特殊的类,用于声明一组固定且确定的常量

自定义枚举类

在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
public class Main {
public static void main(String[] args) {
DayOfWeek day1 = DayOfWeek.MONDAY;
DayOfWeek day2 = DayOfWeek.FRIDAY;

System.out.println(day1); // 输出:MONDAY
System.out.println(day2); // 输出:FRIDAY

System.out.println(day1.getDisplayName()); // 输出:星期一
System.out.println(day2.getDisplayName()); // 输出:星期五
}
}

// 自定义枚举类
public final class DayOfWeek {
public static final DayOfWeek MONDAY = new DayOfWeek("星期一"); // 星期一
public static final DayOfWeek TUESDAY = new DayOfWeek("星期二"); // 星期二
public static final DayOfWeek WEDNESDAY = new DayOfWeek("星期三"); // 星期三
public static final DayOfWeek THURSDAY = new DayOfWeek("星期四"); // 星期四
public static final DayOfWeek FRIDAY = new DayOfWeek("星期五"); // 星期五
public static final DayOfWeek SATURDAY = new DayOfWeek("星期六"); // 星期六
public static final DayOfWeek SUNDAY = new DayOfWeek("星期日"); // 星期日

private final String displayName; // 显示名称

private DayOfWeek(String displayName) {
this.displayName = displayName;
}

public String getDisplayName() {
return displayName;
}
}

使用enum关键字定义枚举类

jdk5.0之后,可以使用enum关键字定义枚举类

  • 枚举类对象必须在第一行声明,多个枚举类对象之间用”,”隔开,用”;”结束

  • 枚举类中的属性,系统会自动添加 public static final 修饰

  • 枚举类的构造器只能使用 private 权限修饰符,系统会自动添加 privatel 修饰

使用enum关键字定义的枚举类,默认继承于java.long.Enum类,因此不能继承其他类,但所有枚举实例都可以调用 Enum 类中的方法

方法名称说明
values()返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。
valueOf(String str)返回枚举类中对象名是str的对象,如果没有str的枚举类对象,则抛异常:IllegalArgumentException。
toString()返回当前枚举类对象常量的名称
compareTo()比较两个枚举成员在定义时的顺序
ordinal()获取枚举成员的索引位置
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
public class Demo {
public static void main(String[] args) {
DayOfWeek day1 = DayOfWeek.MONDAY;
DayOfWeek day2 = DayOfWeek.FRIDAY;

System.out.println(day1); // 输出:MONDAY
System.out.println(day2); // 输出:FRIDAY

System.out.println(day1.getDisplayName()); // 输出:星期一
System.out.println(day2.getDisplayName()); // 输出:星期五

// values()方法:返回枚举类型的对象数组
DayOfWeek[] values = DayOfWeek.values();
for (DayOfWeek day : values) {
System.out.println(day);
}

// valueOf(String str):返回指定枚举类对象,如果没有这个枚举类对象,则抛异常:IllegalArgumentException。
DayOfWeek day = DayOfWeek.valueOf("MONDAY");
System.out.println(day);

// ordinal():获取枚举成员的索引位置
int ordinal = DayOfWeek.MONDAY.ordinal();
System.out.println(ordinal);
}
}

// 使用enum关键字定义枚举类
enum DayOfWeek {
// 提供当前枚举类的多个对象,多个对象之间用","隔开,用";"结束
MONDAY("星期一"),
TUESDAY("星期二"),
WEDNESDAY("星期三"),
THURSDAY("星期四"),
FRIDAY("星期五"),
SATURDAY("星期六"),
SUNDAY("星期日");

// 声明对象的属性,private final修饰
private final String displayName;

// 私有化的构造器,并给对象属性初始化
DayOfWeek(String displayName) {
this.displayName = displayName;
}

// 获取枚举类对象的显示名称
public String getDisplayName() {
return displayName;
}

@Override
public String toString() {
return name(); // 返回枚举常量的名称,如"MONDAY"、"TUESDAY"等
}
}