Java基础部分2

Java 基础知识

一、 package 和 import

package

其实包就是把很多很多的代码进行整理,便于代码的统一管理。

①怎么定义包:在java源码第一行编写 package 语句。注意:package语句只能出现在java代码第一行。

②包名命名规范中要求是全部小写。

③包名命名规范:公司域名倒序 + 项目名 + 模块名 + 功能名。例如:

①com.powernode.oa.empgt.service

④如果带包编译:

①javac -d 编译后的存放目录 java源文件路径

⑥有了包机制后,完整类名是包含包名的,例如类名是:com.powernode.javase.chapter02.PackageTest

import

①import语句用来引入其他类。

②A类中使用B类,A类和B类不在同一个包下时,就需要在A类中使用import引入B类。

③java.lang包下的不需要手动引入。

④import语句只能出现在package语句之下,class定义之前。

⑤import语句可以编写多个。

⑥import语句可以模糊导入:java.util.*;

⑦import静态导入:import static java.lang.System.*;

image-20240223194626584image-20240223194657595

二、面向对象概述

1. 面向对象三大特征

面向对象三大特征

①封装(Encapsulation)

②继承(Inheritance)

③多态(Polymorphism)

2.对象的创建与使用

1
2
3
4
5
6
7
8
9
10
11
public class Student {
// 姓名
String name; // 实例变量
// 年龄
int age;
// 性别
boolean gender;
// 学习
public void study(){ System.out.println(“正在学习”); } // 实例方法
}

①对象的创建

1
Student s = new Student();

在Java中,使用class定义的类,属于引用数据类型。所以Student属于引用数据类型。类型名为:Student。

Student s; 表示定义一个变量。数据类型是Student。变量名是s。

②对象的使用

读取属性值:s.name

修改属性值:s.name = “jackson”;

③通过一个类可以实例化多个对象

Student s1 = new Student();

Student s2 = new Student();

④ public static void functionA(),直接用类名.functionA()来访问

这个叫做静态方法

如果描述对象的动作,就不加static ,这种方法叫做实例方法

3.JVM内存结构图

image-20240223195906110

  1. 元空间(mataspace)存储的是类的元信息,字节码等。元空间是在java8后面引入的。

JVM java虚拟机中定义的规范叫做方法区。

image-20240223202022775

==一旦引用为NULL,表示引用不再指向对象了==

image-20240223202252613

4.封装

什么是封装

封装是一种将数据和方法加以包装,使之成为一个独立的实体,并且把它与外部对象隔离开来的机制。具体来说,封装是将一个对象的所有“状态(属性)”以及“行为(方法)”统一封装到一个类中,从而隐藏了对象内部的具体实现细节,向外界提供了有限的访问接口,以实现对对象的保护和隔离。

封装的好处

封装通过限制外部对对象内部的直接访问和修改,保证了数据的安全性,并提高了代码的可维护性和可复用性。

在代码上如何实现封装

1. 属性私有化,对外提供getter和setter方法。

属性私有化:使用private来进行修饰。

属性私有化的作用是禁止外部程序随意访问。

2. 为了保证外部程序以然可以访问age属性,还是需要提供一个公开的外部访问的入口。

访问一般分为两个方法,一个负责读,一个负责修改。

读取方法的格式:

1
public int getAge(){}

改方法的格式:

1
public void setAge(int age){}

实例方法调用实例方法

image-20240225145941617

还是一个this.pay()

5. 构造方法(构造器)constructor

其实就是C里面的构造函数

构造方法的作用

  1. 实现对象的出啊关键,通过调用的构造方法啊可以完成对于对象的创建
  2. 实现对于对象的各种属性的赋值。

构造方法怎么定义?

image-20240225150328362

构造方法最后执行结束之后,会返回这个new出来的实例的内存地址,但是构造方法中不需要提供return语句

构造方法名一定要和类名保持一致!

构造方法不需要返回值,如果有返回值就变成了普通方法

缺省构造器

如果一个类中没有显示的去定义构造方法,就会提供一个默认的方法,叫做缺省构造器。

当在调用 new Classname的时候,其实就是在调用构造函数

image-20240225151158129

完成构建,属性赋默认值。

image-20240225154836218

如果一个类里面提供了构造函数,那么就不会再创建缺省的构造方法,所以最好还是写一个无参数的构造方法。

构造方法Constructor(构造器)

①构造方法有什么作用?

1.构造方法的执行分为两个阶段:对象的创建和对象的初始化。这两个阶段不能颠倒,也不可分割。

2.在Java中,当我们使用关键字new时,就会在内存中创建一个新的对象,虽然对象已经被创建出来了,但还没有被初始化。而初始化则是在执行构造方法体时进行的。

②构造方法如何定义?

[修饰符列表] 构造方法名(形参){}

③构造方法如何调用?new 构造方法名(实参);

④关于无参数构造方法:如果一个类没有显示的定义任何构造方法,系统会默认提供一个无参数构造方法,也被称为缺省构造器。一旦显示的定义了构造方法,则缺省构造器将不存在。为了方便对象的创建,建议将缺省构造器显示的定义出来。

⑤构造方法支持重载机制。

⑥关于构造代码块。对象的创建和初始化过程梳理:

①new的时候在堆内存中开辟空间,给所有属性赋默认值

②执行构造代码块进行初始化

③执行构造方法体进行初始化

④构造方法执行结束,对象初始化完毕。

注意,set是修改,构造方法是初始化

6. this关键字

this关键字

①this是一个关键字。

②this出现在实例方法中,代表当前对象。语法是:this.

③this本质上是一个==引用==,该引用保存当前对象的内存地址。

④通过“this.”可以访问实例变量,可以调用实例方法。

⑤this存储在:栈帧的局部变量表的第0个槽位上。

⑥this. 大部分情况下可以省略,用于区分局部变量和实例变量时不能省略。

⑦this不能出现在静态方法中。

public static vois functionstatic(){}

这里面不可以用this,因为static方法中没有当前对象,所以不可以用this。

但是在静态方法中可以调用另外一个静态方法。

⑧“this(实参)”语法:

①只能出现在构造方法的第一行。

②通过当前构造方法去调用本类中其他的构造方法。

③作用是:代码复用。

实例方法只可以引用.

7.static关键字

final变量不能修改,static变量可以在这个类里头修改

①static是一个关键字,翻译为:静态的。

②static修饰的变量叫做静态变量。当所有对象的某个属性的值是相同的,建议将该属性定义为静态变量,来节省内存的开销。

③静态变量在类加载时初始化,存储在堆中。

④static修饰的方法叫做静态方法。

所有static级别的,都是类级别的,直接用类名来访问

⑤所有静态变量和静态方法,统一使用“类名.”调用。==虽然可以使用“引用.”来调用,但实际运行时和对象无关,所以不建议这样写,因为这样写会给其他人造成疑惑。==

⑥使用“引用.”访问静态相关的,即使引用为null,也不会出现空指针异常。

⑦静态方法中不能使用this关键字。因此无法直接访问实例变量和调用实例方法。

⑧静态代码块在类加载时执行,一个类中可以编写多个静态代码块,遵循自上而下的顺序依次执行。

⑨静态代码块代表了类加载时刻,如果你有代码需要在此时刻执行,可以将该代码放到静态代码块中。

静态变量内存图

image-20240225194625857

image-20240225194714666

空指针异常

image-20240225195106209

静态方法中无法直接访问实例相关的数据。

静态代码块

image-20240225195527691

语法结构
  1. 语法格式:

    1
    2
    3
    static{
    这个括号叫做静态上下文,
    }
  2. 静态代码块在类加载的时候执行,并且只执行了一次

  3. 静态代码块可以编写多个,并且遵循自上而下的顺序执行。

image-20240225212935175

按照以上的这个代码,main方法也是最后执行的

此外

image-20240225213020734

按照这个,会报错。连main方法都没有执行,name也没有执行,

:在静态上下文中无法直接访问实例相关的数据,但是可以访问在这个静态代码块前面定义的静态数据。

在这个静态代码块之后定义的就不能访问了。因为静态的数据是按照顺序进行的。

image-20240225213131315

8.JVMjava虚拟机的体系结构

==JVM对应了一套规范(Java虚拟机规范),它可以有不同的实现==

①JVM规范是一种抽象的概念,它可以有多种不同的实现。例如:

1.HotSpot:HotSpot 由 Oracle 公司开发,是目前最常用的虚拟机实现,也是默认的 Java 虚拟机,默认包含在 Oracle JDK 和 OpenJDK 中

2.JRockit:JRockit 也是由 Oracle 公司开发。它是一款针对生产环境优化的 JVM 实现,能够提供高性能和可伸缩性

3.IBM JDK:IBM JDK 是 IBM 公司开发的 Java 环境,采用了与 HotSpot 不同的 J9 VM,能够提供更小的内存占用和更迅速的启动时间

4.Azul Zing:Azul Zing 是针对生产环境优化的虚拟机实现,能够提供高性能和实时处理能力,适合于高负载的企业应用和实时分析等场景

5.OpenJ9:OpenJ9 是由 IBM 开发的优化的 Java 虚拟机实现,支持高度轻量级、低时延的 GC、优化的 JIT 编译器和用于健康度测试的可观察性仪表板

②右图是从oracle官网上截取的Java虚拟机规范中的一部分。(大家也可以找一下oracle官方文档)

③我们主要研究运行时数据区。运行时数据区包括6部分:

  1. The pc Register(程序计数器):下一个你要执行的字节码指令

  2. Java Virtual Machine Stacks(Java虚拟机栈)

  3. Heap(堆)

  4. Method Area(方法区)

  5. Run-Time Constant Pool(运行时常量池)

  6. Native Method Stacks(本地方法栈)

1
2
3
4
5
6
7
8
9
10
11
JVM规范中的运行时数据区
The pc Register(程序计数器):是一块较小的内存空间,此计数器记录的是正在执行的虚拟机字节码指令的地址;
Java Virtual Machine Stacks(Java虚拟机栈):Java虚拟机栈用于存储栈帧。栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
Heap(堆):是Java虚拟机所管理的最大的一块内存。堆内存用于存放Java对象实例以及数组。堆是垃圾收集器收集垃圾的主要区域。
Method Area(方法区):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
这个是规范,但是在实现中有些会加到堆里
Run-Time Constant Pool(运行时常量池):是方法区的一部分,用于存放编译期生成的各种字面量与符号引用(类名、属性名等)。
Native Method Stacks(本地方法栈):在本地方法的执行过程中,会使用到本地方法栈。和 Java 虚拟机栈十分相似。


总结:这些运行时数据区虽然在功能上有所区别,但在整个 Java 虚拟机启动时都需要被创建,并且在虚拟机运行期间始终存在,直到虚拟机停止运行时被销毁。同时,不同的 JVM 实现对运行时数据区的分配和管理方式也可能不同,会对性能和功能产生影响。

**image-20240225214223891

image-20240225214331825

9、设计模式概述

单例模式(==GoF==23种设计模式之一,最简单的设计模式:==如何保证某种类型的对象只创建一个)==

image-20240225220833968

//饿汉式单例模式:不管这个对象用还是不用,提前把对象给创建好了。

step1:构造函数私有化//不让new,但是又可以获得方法

step2:对外提供一个公开的静态方法,用这个方法获取单个实例

step3: 定义一个静态变量,在类加载的时候,初始化静态变量(只初始化一次)。

//懒汉式单例模式:等到用的时候再创建,不用不创建。

step1:构造方法私有化

step2:对外提供一个静态方法,可以通过这个方法获取到对象;

这个是初步的,等到以后多线程的时候,还需要 改进

10.继承

① 面向对象三大特征之一:继承

② 继承作用?

l基本作用:代码复用

l重要作用:有了继承,才有了方法覆盖和多态机制。

③继承在java中如何实现?

l[修饰符列表] class 类名 extends 父类名{}

lextends翻译为扩展。表示子类继承父类后,子类是对父类的扩展。

④继承相关的术语:当B类继承A类时

lA类称为:父类、超类、基类、superclass

lB类称为:子类、派生类、subclass

⑤Java只支持单继承,一个类只能直接继承一个类。

⑥Java不支持多继承,但支持多重继承(多层继承)。

⑦子类继承父类后,除私有的不支持继承、构造方法不支持继承。其它的全部会继承。

==⑧一个类没有显示继承任何类时,默认继承java.lang.Object类。==比如tostring就是object的,任何一个类都可以调用

image-20240225221336300

11.方法覆盖

方法覆盖/override/方法重写/overwrite,是在编译器层面的功能,方法重载机制是给编译器看的

①什么情况下考虑使用方法覆盖?

1.当从父类中继承过来的方法无法满足当前子类的业务需求时。

②发生方法覆盖的条件?

  1. 具有==继承关系的父子类之间==

  2. ==相同的返回值类型,相同的方法名,相同的形式参数列表==

  3. 访问权限不能变低,可以变高。

image-20240225225653075

高———————>低

  1. 抛出异常不能变多,可以变少。

父类抛了,子类不抛,比如

  1. 返回值类型可以是父类方法返回值类型的子类。

③方法覆盖的小细节:

  1. @Override注解标注的方法会在编译阶段检查该方法是否重写了父类的方法。

  2. ==私有方法不能继承,所以不能覆盖。==

  3. ==构造方法不能继承,所以不能覆盖。==

  4. ==静态方法不存在方法覆盖,方法覆盖针对的是实例方法。==

  5. ==方法覆盖说的实例方法,和实例变量无关。(==可以写程序测试一下)

image-20240225224843910

image-20240225224915679

在java中也有个注解,在编译的时候可以检查这个方法是否是重写了父类的方法@Override

@Override 只在编译阶段会有用,和运行期无关.

如果返回值类型是引用类型,那么这个返回值类型可以是原类型的子类型

image-20240225225250788

12.多态

多态的基础语法

①什么是向上转型和向下转型?

  1. java允许具有继承关系的父子类型之间的类型转换。

  2. ==向上转型(upcasting):子–>父==

==l子类型的对象可以赋值给一个父类型的引用。==

  1. ==向下转型(downcasting):父–>子==

一般来说,想要调用的方法是子类当中特有的方法的时候,才会想要向下转型.

==l父类型的引用可以转换为子==类型的引用。但是需要加强制类型转换符。

  1. 无论是向上转型还是向下转型,前提条件是:==两种类型之间必须存在继承关系。这样编译器才能编译通过。==

②什么是多态?

编译过程中是一个形态,运行的时候又是另外一个形态.

  1. 父类型引用指向子类对象。Animal a = new Cat(); a.move();

  2. 程序分为编译阶段和运行阶段:

l编译阶段:编译器只知道a是Animal类型,因此去Animal类中找move()方法,找到之后,绑定成功,编译通过。这个过程通常被称为静态绑定。

l运行阶段:运行时和JVM堆内存中的真实Java对象有关,所以运行时会自动调用真实对象的move()方法。这个过程通常被称为动态绑定。

  1. 多态指的是:多种形态,编译阶段一种形态,运行阶段另一种形态,因此叫做多态。

image-20240225231010553

image-20240225231835927

③ instanceof运算符可以解决类型转换问题

  1. instanceof运算符的结果一定是:true/false
  2. 语法结构
1
2
3
(a instanceof cat)
true表示:a引用指向的对象是cat类型
false表示:a引用的指向的对象不是cat

这个运算符主要就是用来进行判断.

1
2
3
if(x instanceof Bird){
Bird y =(Bird)x;
}

④ 软件开发七大原则

软件开发原则旨在引导软件行业的从业者在代码设计和开发过程中,遵循一些基本原则,以达到高质量、易维护、易扩展、安全性强等目标。软件开发原则与具体的编程语言无关的,属于软件设计方面的知识。

  1. ==开闭原则 (Open-Closed Principle,OCP):一个软件实体应该对扩展开放,对修改关闭。即在不修改原有代码的基础上,通过添加新的代码来扩展功能。(最基本的原则,其它原则都是为这个原则服务的。)==

  2. 单一职责原则:一个类只负责单一的职责,也就是一个类只有一个引起它变化的原因。

  3. 里氏替换原则:子类对象可以替换其基类对象出现的任何地方,并且保证原有程序的正确性。

  4. 接口隔离原则:客户端不应该依赖它不需要的接口。

  5. 依赖倒置原则:高层模块不应该依赖底层模块,它们都应该依赖于抽象接口。换言之,面向接口编程。

  6. 迪米特法则:一个对象应该对其它对象保持最少的了解。即一个类应该对自己需要耦合或调用的类知道得最少。

  7. 合成复用原则:尽量使用对象组合和聚合,而不是继承来达到复用的目的。组合和聚合可以在获取外部对象的方法中被调用,是一种运行时关联,而继承则是一种编译时关联。

⑤ 多态在开发中的作用

  1. 降低程序的耦合度,提高程序的扩展力。

  2. 尽量使用多态,面向抽象编程,不要面向具体编程。

⑥多态的基础语法

向下转型我们需要注意什么?

  1. 向下转型时,使用不当,容易发生类型转换异常:ClassCastException。

  2. 在向下转型时,一般建议使用instanceof运算符进行判断来避免ClassCastException的发生。

instanceof运算符的使用

  1. 语法格式:(引用 instanceof 类型)

  2. 执行结果是true或者false

  3. 例如:(a instanceof Cat)

l如果结果是true:表示a引用指向的对象是Cat类型的。

l如果结果是false:表示a引用指向的对象不是Cat类型的。

13 . 抽象类

注意!抽象类不一定有抽象方法,但是有抽象方法的类一定是抽象类。

此外,public和abstract没有顺序关系

①什么时候考虑将类定义为抽象类?

如果类中有些方法无法实现或者没有意义,可以将方法定义为抽象方法。类定义为抽象类。这样在抽象类中只提供公共代码,具体的实现强行交给子类去做。比如一个Person类有一个问候的方法greet(),但是不同国家的人问候的方式不同,因此greet()方法具体实现应该交给子类。再比如主人喂养宠物的例子中的宠物Pet,Pet中的eat()方法的方法体就是没有意义的。

②抽象类如何定义?

abstract class 类名{}

③抽象类有构造方法,但无法实例化。抽象类的构造方法是给子类使用的。

④抽象方法如何定义?

abstract 方法返回值类型 方法名(形参);

没有方法体

⑤抽象类中不一定有抽象方法,但如果有抽象方法那么类要求必须是抽象类。

==⑥一个非抽象的类继承extend抽象类,要求必须将抽象方法进行实现/重写。==

需要把全部的抽象方法全部给实现了

⑦abstract关键字不能和private,final,static关键字共存。

private: private是不能被继承覆盖的,而abstract修饰的是一定要被继承实现的,存在冲突.

final: 不能继承

static:不能被覆盖

⑧抽象类需要构造方法,但是无法实现实例化,这个构造方法是给其子类使用的.

14. super关键字

①super关键字和this关键字对比来学习。this代表的是当前对象。super代表的是当前对象中的父类型特征。

②super不能使用在静态上下文中。就是在编写

③“super.”大部分情况下是可以省略的。什么时候不能省略?

==当父类和子类中定义了相同的属性(实例变量)或者相同方法(实例方法)时,如果需要在子类中访问父类的属性或方法时,super.不能省略。==

④this可以单独输出,super不能单独输出。

this本身是一个引用,所以可以直接输出,super不是引用,不能直接输出

1
2
system.out.println(this);//√
System.out.println(super);//×

⑤super(实参); 通过子类的构造方法调用父类的构造方法,目的是为了完成父类型特征的初始化。

⑥当一个构造方法第一行没有显示的调用“super(实参);”,也没有显示的调用“this(实参)”,系统会自动调用super()。因此一个类中的无参数构造方法建议显示的定义出来。

⑦super(实参); 这个语法只能出现在构造方法第一行。

this()和super()

⑧在Java语言中只要new对象,Object的无参数构造方法一定会执行。

image-20240226111210195

image-20240226112229142

子类的构造方法中,就算没有写super();,也是会跑这个代码.

1
2
3
4
5
6
super();
会跑父类的构造方法.也会给父类的字段赋值.
当一个构造方法,没有显示的写this(),也没有显示的写super(),系统会自动的调用super()
当然也可以是:super(actno , balance);
调用父类的方法: super.doSome();

15.final关键字

这个关键字表示是最终的,被final所修饰的类不能被 继承

①final修饰的类不能被继承

②final修饰的方法不能被覆盖

③final修饰的变量,一旦赋值不能重新赋值

④final修饰的实例变量必须==在对象初始化时==手动赋值

必须在构造方法执行完之前,手动附上值.不允许采用系统的默认值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
final class Mystring{
public final void m(){

}
}
//比如String就是不能被继承

final String name;//×
或者
final String name;
public User(String name , int age){
this.name = name;
this.age = age;

}

⑤final修饰的实例变量一般和static联合使用:==称为常量==

因为类中定义的final 修饰的变量不能修改,最直接的理解就是所有的实例变量的这个变量都是一个值,因此,假设new出来了100个,那么这100个类的这个字段也都是同一个,所以可以直接加上static,设置成一个静态的,更方便,也更省堆空间.

1
static final double MATH_PAI = 3.1415926

⑥final修饰的引用,一旦指向某个对象后,不能再指向其它对象。但指向的对象内部的数据是可以修改的。

1
final 修饰对象,内存地址不能改,但是内存地址里面的东西还是可以修改的

image-20240226150228776

16.接口

接口的基础语法

==①接口(interface)在Java中表示一种规范或契约,它定义了一组抽象方法和常量,用来描述一些实现这个接口的类应该具有哪些行为和属性==接口和类一样,也是一种引用数据类型

②接口怎么定义?[修饰符列表] interface 接口名{}

1
2
3
public interface Myinterface{
里面的方法可不能有方法体
}

③抽象类是半抽象的(抽象类可以定义抽象方法也可以定义非抽象方法 ,有构造方法不能new对象),

​ 接口是完全抽象的。==接口没有构造方法,也无法实例化。==

④接口中只能定义:常量+抽象方法。==接口中的常量的static final可以省略==。接口中的抽象方法的abstract可以省略。接口中所有的方法和变量都是public修饰的。(JDK8)

⑤接口和接口之间可以多继承。

⑥类和接口的关系我们叫做实现(**==这里的实现也可以等同看做继承==)。使用==implements==**关键字进行接口的实现。

⑦一个非抽象的类实现接口必须将接口中所有的抽象方法全部实现。

⑧一个类可以实现多个接口。语法是:class 类 implements 接口A,接口B{}

image-20240226161310540

⑨Java8之后,接口中允许出现默认方法和静态方法(JDK8新特性)

引入默认方式是为了解决接口演变问题:接口可以定义抽象方法,但是不能实现这些方法。所有实现接口的类都必须实现这些抽象方法。这会导致接口升级的问题:当我们向接口添加或删除一个抽象方法时,这会破坏该接口的所有实现,并且所有该接口的用户都必须修改其代码才能适应更改。这就是所谓的”接口演变”问题。

如果还是按照之前的,那么如果需要在接口中加入一个新的方法,按照接口的实现类需要把借口中的全部都给实现了这一个要求,这个接口的所有实现类都需要实现这个接口,这太麻烦了.

加入默认方法后,实现该接口的实现类中都有这个默认方法,但是不需要去实现

引入的静态方法只能使用本接口名来访问,无法使用实现类的类名访问。

原因:接口也可以当工具使用,渐渐变成了工具类

默认方法:

image-20240226162231265

静态方法:

1
2
3
4
5
6
7
8
9
static void staticmethod(){


}

//调用
MyInterface.staticmethod();
//不可以
MyInterfaceImpl.staticmethod();

image-20240226162707551⑩JDK9之后允许接口中定义私有private的实例方法(为默认方法服务的)和私有的静态方法(为静态方法服务的)。

⑪所有的接口隐式的继承Object。因此接口也可以调用Object类的相关方法。

接口的作用

①面向接口调用的称为:接口调用者

②面向接口实现的称为:接口实现者

③调用者和实现者通过接口达到了解耦合。也就是说调用者不需要关心具体的实现者,实现者也不需要关心具体的调用者,双方都遵循规范,面向接口进行开发。

面向抽象编程,面向接口编程,可以降低程序的耦合度,提高程序的==扩展力==。

⑤例如定义一个Usb接口,提供read()和write()方法,通过read()方法读,通过write()方法写:

①定义一个电脑类Computer,它是调用者,面向Usb接口来调用。

②Usb接口的实现可以有很多,例如:打印机(Printer),硬盘(HardDrive)。

1
2
3
4
5
6
7
8
9
10
11
public class Computer{

public void conn(Usb usb){

usb.read();

usb.write();

}

}

⑦再想想,我们平时去饭店吃饭,这个场景中有没有接口呢?食谱菜单就是接口。顾客是调用者。厨师是实现者。

接口与抽象类如何选择

①抽象类和接口虽然在代码角度都能达到同样的效果,但适用场景不同:

​ 抽象类主要适用于公共代码的提取。当多个类中有共同的属性和方法时,为了达到代码的复用,建议为这几个类提取出来一个父类,在该父类中编写公共的代码。如果有一些方法无法在该类中实现,可以延迟到子类中实现。这样的类就应该使用抽象类。

接口主要用于功能的扩展。例如有很多类,一些类需要这个方法,另外一些类不需要这个方法时,可以将该方法定义到接口中。需要这个方法的类就去实现这个接口,不需要这个方法的就可以不实现这个接口。接口主要规定的是行为。

注意:一个类继承某个类的同时可以实现多个接口:class 类 extends 父类 implements 接口A,接口B{}

注意:当某种类型向下转型为某个接口类型时,接口类型和该类之间可以没有继承关系,编译器不会报错的。

17. UML(统一建模语言概述)

考虑一下类之间的关系.

①UML(Unified Modeling Language,统一建模语言)是一种用于面向对象软件开发的图形化的建模语言。它由Grady Booch、James Rumbaugh和Ivar Jacobson等三位著名的软件工程师所开发,并于1997年正式发布。UML提供了一套通用的图形化符号和规范,帮助开发人员以图形化的形式表达软件设计和编写的所有关键方面,从而更好地展示软件系统的设计和实现过程。

②UML是一种图形化的语言,类似于现实生活中建筑工程师画的建筑图纸,图纸上有特定的符号代表特殊的含义。

③UML不是专门为java语言准备的。只要是面向对象的编程语言,开发前的设计,都需要画UML图进行系统设计。(设计模式、软件开发七大原则等同样也不是只为java语言准备的。)

④UML图包括:

  • 类图(Class Diagram):描述软件系统中的类、接口、关系和其属性等;
  • 用例图(Use Case Diagram):描述系统的功能需求和用户与系统之间的关系;
  • 序列图(Sequence Diagram):描述对象之间的交互、消息传递和时序约束等;
  • 状态图(Statechart Diagram):描述类或对象的生命周期以及状态之间的转换;
  • 对象图(Object Diagram):表示特定时间的系统状态,并显示其包含的对象及其属性;
  • 协作图(Collaboration Diagram):描述对象之间的协作,表示对象之间相互合作来完成任务的关系;
  • 活动图(Activity Diagram):描述系统的动态行为和流程,包括控制流和对象流;
  • 部署图(Deployment Diagram):描述软件或系统在不同物理设备上部署的情况,包括计算机、网络、中间件、应用程序等。

⑤常见的UML建模工具有:StarUML,Rational Rose等。

类之间的关系

  1. 泛化关系(is a)//继承关系
1
2
3
4
泛化关系(继承关系)is a关系。Cat is a Animal
public class Animal{ }
public class Cat extends Animal{ }
public class Dog extends Animal{ }

image-20240226173319940

  1. 实现关系(is like a)//定义一个接口,然后有一个实现接口类
1
2
3
4
实现关系 is like a 关系。
public interface Usb{}
public class Printer implements Usb{}
public class HardDrive implements Usb{}

image-20240226173405538

  1. 关联关系(has a)
1
2
3
4
5
6
7
8
9
关联关系 has a
public class Course{

}
public class Student {
// 实例变量(属性)
Course course;
}
A里面有B

image-20240226173521101

  1. 聚合关系

聚合关系指的是一个类包含、合成或者拥有另一个类的实例,而这个实例是可以独立存在的。聚合关系是一种弱关联关系,表示整体与部分之间的关系。例如一个教室有多个学生

image-20240226174835150

==生命不绑定在一起==

  1. 组合关系(Composition)

组合关系是聚合关系的一种特殊情况,表示整体与部分之间的关系更加强烈。组合关系指的是一个类包含、合成或者拥有另一个类的实例,而这个实例只能同时存在于一个整体对象中。如果整体对象被销毁,那么部分对象也会被销毁。==例如一个人对应四个肢体==。

1
2
3
4
5
6
7
public class Limbs{

}

public class Person{
List<Limbs> limbs;
}

image-20240226175009462

  1. 依赖关系(Dependency)

依赖关系是一种临时性的关系,**==当一个类使用另一个类的功能时==**,就会产生依赖关系。如果一个类的改变会影响到另一个类的功能,那么这两个类之间就存在依赖关系。依赖关系是一种较弱的关系,可以存在多个依赖于同一个类的对象。==例如A类中使用了B类,但是B类作为A类的方法参数或者局部变量等。==

image-20240226175047202

1
2
3
4
5
6
7
8
9
10
public class User{
public void doSome(A a){

}
public void doOther(){
A a;
}
}

public class A {}

==18. 访问控制权限==

image-20240226175206352

①private:私有的,只能在本类中访问。

②缺省:默认的,同一个包下可以访问。

③protected:受保护的,子类中可以访问。(==受保护的通常就是给子孙用的。==)clone就是protected的

④public:公共的,在任何位置都可以访问。

==①类中的属性和方法访问权限共有四种:private、缺省、protected和public。==

==②类的访问权限只有两种:public和 缺省。==

1
2
3
4
5
6
7
8
9
10
public class A{

}
class B{

}
只有以上两种是正确的
protected class C{
//这个就是错误的
}

③访问权限控制符不能修饰局部变量。

注意:class里面的属性,方法的访问权限是四个;但是对于定义类,是只有两种访问权限的.!

image-20240226175919594

19.Object类

①java.lang.Object是所有类的超类。java中所有类都实现了这个类中的方法。

②Object类是我们学习JDK类库的第一个类。通过这个类的学习要求掌握会查阅API帮助文档。

③现阶段Object类中需要掌握的方法:

toString:将java对象转换成字符串。

image-20240226180456350

equals:判断两个对象是否相等。//返回一个布尔类型

1
2
3
4
5
6
7
8
9
10
11
12
== 的作用:

  基本类型:比较的就是值是否相同

  引用类型:比较的就是地址值是否相同
  
equals 的作用:

  引用类型:默认情况下,比较的是地址值。

注:不过,我们可以根据情况自己重写该方法。一般重写都是自动生成,比较对象的成员变量值是否相同
String类中被复写的equals()方法其实是比较两个字符串的内容

④现阶段Object类中需要了解的方法:

hashCode:返回一个对象的哈希值,通常作为在哈希表中查找该对象的键值。Object类的默认实现是根据对象的内存地址生成一个哈希码(即将对象的内存地址转换为整数作为哈希值)。hashCode()方法是为了HashMap、Hashtable、HashSet等集合类进行优化而设置的,以便更快地查找和存储对象。

finalize:当java对象被回收时,由GC自动调用被回收对象的finalize方法,通常在该方法中完成销毁前的准备。

==clone:对象的拷贝。(浅拷贝,深拷贝)==

Java深入理解深拷贝和浅拷贝区别_java深拷贝浅拷贝-CSDN博客

浅拷贝:

Object的默认方法,专门给子类使用的,受保护的,c++实现的

image-20240226181415375

user和usertest不符合三个要求,不能进行clone,那么怎么实现呢?

重写!

1.==建议把重写的修饰符改为public==

2.要实现接口 ==implements Cloneable==(这是一个标志接口,给java虚拟机看的)

image-20240226181815569

protected修饰的只能在同一个包下或者子类中访问。

只有实现了Cloneable接口的对象才能被克隆。

image-20240226182500898

image-20240226182936458

深拷贝:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//这是浅拷贝
public Object clone() throws CloneNotSupportedException {
Object object = super.clone();
return object;
}
//这是深拷贝
public Object clone() throws CloneNotSupportedException {
// 浅复制时:
// Object object = super.clone();
// return object;

// 改为深复制:
Student student = (Student) super.clone();
// 本来是浅复制,现在将Teacher对象复制一份并重新set进来
student.setTeacher((Teacher) student.getTeacher().clone());
return student;

}

20.clone

直接赋值

直接赋值的方式没有生产新的对象,只是生新增了一个对象引用**,直接赋值在 Java 内存中的模型大概是这样的

image-20240226184438712

浅拷贝

如果原型对象的成员变量是值类型,将复制一份给克隆对象,也就是说在堆中拥有独立的空间;

如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。

换句话说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。

image-20240226184739067

也就是新开一个空间,放克隆的对象,但是这个地址里放的还是原来的引用地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Person implements Cloneable {
// 姓名
private String name;
// 年龄
private int age;
// 邮件
private String email;
// 描述
private String desc;

/*
* 重写 clone 方法,需要将权限改成 public ,直接调用父类的 clone 方法就好了
*/
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
...省略...
}\\
***************
person对象:Person{name='张三', age=20, email='123456@qq.com', desc='我是张三'}

person1对象:Person{name='我是张三的克隆对象', age=22, email='123456@qq.com', desc='我是张三'}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class PersonApp {
public static void main(String[] args) throws Exception {
// 初始化一个对象
Person person = new Person("张三",20,"123456@qq.com","我是张三");
// 复制对象
Person person1 = (Person) person.clone();
// 改变 person1 的属性值
person1.setName("我是张三的克隆对象");
// 修改 person age 的值
person1.setAge(22);
System.out.println("person对象:"+person);
System.out.println();
System.out.println("person1对象:"+person1);

}
}

String、Integer 等包装类都是不可变的对象,当需要修改不可变对象的值时,需要在内存中生成一个新的对象来存放新的值,然后将原来的引用指向新的地址,所以在这里我们修改了 person1 对象的 name 属性值,person1 对象的 name 字段指向了内存中新的 name 对象,

但是我们并没有改变 person 对象的 name 字段的指向,所以 person 对象的 name 还是指向内存中原来的 name 地址,也就没有变化

深拷贝

深拷贝也是对象克隆的一种方式,相对于浅拷贝,深拷贝是一种完全拷贝,无论是值类型还是引用类型都会完完全全的拷贝一份,在内存中生成一个新的对象,简单点说就是拷贝对象和被拷贝对象没有任何关系,互不影响。深拷贝的通用模型如下

img

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 PersonDesc implements Cloneable{

// 描述
private String desc;
...省略...
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Person implements Cloneable {
// 姓名
private String name;
// 年龄
private int age;
// 邮件
private String email;

private PersonDesc personDesc;

/**
* clone 方法不是简单的调用super的clone 就好,
*/
@Override
public Object clone() throws CloneNotSupportedException {
Person person = (Person)super.clone();
// 需要将引用对象也克隆一次
person.personDesc = (PersonDesc) personDesc.clone();
return person;
}
...省略...
}

21.内部类

image-20240226185936183

静态内部类:

1
2
3
4
5
6
7
8
9
对于静态内部类来说:访问控制权限修饰符在这里都可以使用。
private static class InnerClass{

}

//怎么创建内部类对象
OuterClass.InnerClass innerClass = new OuterClass.InnerClass();
OuterClass.InnerClass.m4();
innerClass.m3();

由于在创建静态内部类的时候,不用创建外部的那个类,所以不能访问外部类所定义的实例变量和实例方法,但是可以访问静态变量和静态方法,因为静态的(static)是刚开始运行的时候就会跑的,写入编译文件中的

实例内部类

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
public class OuterClass {
// 实例变量
public int i = 100;
// 实例方法
public void m1(){
System.out.println("外部类的实例方法m1执行了");
}

// 静态变量
private static int j = 200;
// 静态方法
public static void m2(){
System.out.println("外部类的静态方法m2执行了");
}

// 实例内部类
// 也可以使用访问权限修饰符修饰。
public class InnerClass {
public void x(){
System.out.println(i);
System.out.println(j);
m1();
m2();
}
}

}

public class OuterClassTest {
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
System.out.println(outerClass.i);
OuterClass.InnerClass innerClass = outerClass.new InnerClass();
innerClass.x();
}
}

局部内部类

①局部内部类:和局部变量一个级别

局部内部类方外类外部的局部变量时,局部变量需要被final修饰。

从JDK8开始,不需要手动添加final了,但JVM会自动添加。

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 OuterClass {

// 静态变量
private static int k = 1;
// 实例变量
private int f = 2;

public void m1(){
// 局部变量
int i = 100;
// 局部内部类
class InnerClass {
// 实例方法
public void x(){
System.out.println(k);
System.out.println(f);
System.out.println(i);
}
}
// new对象
InnerClass innerClass = new InnerClass();
innerClass.x();
//这两行执行了,这个内部类就要执行,内部类执行就需要这个实例方法执行,这个实例方法执行,就要这个外部类创建,所以在不受限制的情况下,实例变量/方法和静态变量/方法是都可以访问的,但是不可以改,是默认加final的

}
}
/**
* 局部内部类:等同于局部变量。
*
* 结论:局部内部类能不能访问外部类的数据,取决于局部内部类所在的方法。
* 如果这个方法是静态的:只能访问外部类中静态的。
* 如果这个方法是实例的:可以都访问。
*
* 局部内部类不能使用访问权限修饰符修饰。
*
* 局部内部类在访问外部的局部变量时,这个局部变量必须是final的。只不过从JDK8开始。这个final关键字不需要提供了。系统自动提供。
*/

==匿名内部类==

特殊的局部内部类,没有名字,只能用一次。

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
package com.powernode.javase.oop45;

/**
* 匿名内部类:没有名字的类。只能使用一次。
*/
public class Test {
public static void main(String[] args) {
// 创建电脑对象
Computer computer = new Computer();
//computer.conn(new Printer());

// 以下conn方法参数上的代码做了两件事:
// 第一:完成了匿名内部类的定义。
// 第二:同时实例化了一个匿名内部类的对象。
computer.conn(new Usb(){
// 接口的实现
@Override
public void read() {
System.out.println("read.....");
}

@Override
public void write() {
System.out.println("write.....");
}
});
}
}

class Computer {
public void conn(Usb usb){
usb.read();
usb.write();
}
}

interface Usb {
void read();
void write();
}

// 编写一个接口的实现类
/*
class Printer implements Usb {

@Override
public void read() {
System.out.println("打印机开始读取数据");
}

@Override
public void write() {
System.out.println("打印机开始打印");
}
}
*/