freyhe

导航

04.面向对象

面向对象的特征一:封装性

1.为什么要引入封装性?

  • 我们程序设计追求“高内聚,低耦合”。

    ​ 高内聚 :类的内部数据操作细节自己完成,不允许外部干涉;

    ​ 低耦合 :仅对外暴露少量的方法用于使用。

  • 隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。

    通俗的说,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。

2.封装性解决的问题

当我们创建一个类的对象以后,我们可以通过"对象.属性"的方式,对对象的属性进行赋值。

这里,赋值操作要受到属性的数据类型和存储范围的制约。除此之外,没其他制约条件

但是,在实际问题中,我们往往需要给属性赋值加入额外的限制条件。

这个条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加。

(比如:setLegs()同时,我们需要避免用户再使用"对象.属性"的方式对属性进行赋值。则需要将属性声明为私有的(private)

-->此时,针对于属性就体现了封装性。

3.封装性思想具体的代码体现

体现一:将类的属性xxx私化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)此属性的值

​ 隐藏一个类中不需要对外提供的实现细节;

​ 使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑, 限制对属性的不合理操作

​ 便于修改,增强代码的可维护性;

体现二:不对外暴露的私有的方法

体现三:单例模式(将构造器私有化)

体现四:如果不希望类在包外被调用,可以将类设置为缺省的

4.标准类代码JavaBean

JavaBean 是 Java语言编写类的一种标准规范。

符合JavaBean 的类,要求类必须是具体的和公共的,并且具有无参数的构造器,提供用来操作实例变量的setget 方法。

​ 要求:

​ 1.类需要使用 public 修饰

​ 2.成员变量私有化

​ 3.给成员变量提供get/set方法

​ 4.需要有空参数的构造方法

public class ClassName{
//私有化实例变量
//构造器
//无参构造器【必须】
//有参构造器【建议】
//实例方法
//getXxx()
//setXxx()
}

内存图分析

image-20220302075846033

  • 一个对象调用一个方法内存图

image-20220302080008494

  • 两个对象调用同一个方法内存图

image-20220302080043337

  • 将一个对象当做参数传入时的内存图

image-20220302080217955

类与对象

  • 类:具有共同特点(即:属性,成员变量)和行为(即:成员方法)的事物的抽象统称
  • 对象:是一类事物的具体体现。对象是类的一个实例,必然具备该类事物的属性和行为

类的结构之一:属性

对比:属性(成员变量) vs 局部变量

1.相同点:

​ 1.1 定义变量的格式:数据类型 变量名 = 变量值

​ 1.2 先声明,后使用

​ 1.3 变量都其对应的作用域

2.不同点:

​ 2.1 在类中声明的位置的不同

​ 属性(成员变量):直接定义在类的一对{}内

​ 局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量

​ 2.2 关于权限修饰符的不同

​ 属性(成员变量):可以在声明属性时,指明其权限,使用权限修饰符。

常用的权限修饰符:private、public、缺省、protected --->封装性

局部变量:不可以使用权限修饰符

​ 2.3 默认初始化值的情况:

​ 属性:类的属性,根据其类型,都默认初始化值。

​ 整型(byte、short、int、long:0)

​ 浮点型(float、double:0.0)

​ 字符型(char:0 (或'\u0000'))

​ 布尔型(boolean:false)

​ 引用数据类型(类、数组、接口:null)

局部变量:没默认初始化值

​ 意味着,我们在调用局部变量之前,一定要显式赋值

通过 对象名.属性名(或者getXX() 方法) 可以访问到对象的属性(如果对象的属性没有设置值,它会使用默认值

​ 2.4 在内存中加载的位置:

​ 属性(成员变量):加载到堆空间中 (非static)

​ 局部变量:加载到栈空间

类的结构之二:方法

方法:描述类应该具有的功能

方法的声明:

权限修饰符 返回值类型 方法名(形参列表){
    方法体
}

说明:

1 关于权限修饰符:默认方法的权限修饰符先都使用public

Java规定的4种权限修饰符:private、public、缺省、protected -->封装性再细说

2 返回值类型: 返回值 vs 没返回值

​ 如果方法有返回值,则必须在方法声明时,指定返回值的类型。

​ 同时,方法中,需要使用return关键字来返回指定类型的变量或常量:“return 数据”。

​ 如果方法没返回值,则方法声明时,使用void来表示。

​ 通常,没返回值的方法中,就不需要使用return。但是,可以使用“return;”表示结束此方法的意思。

3 方法名:属于标识符,遵循标识符的规则和规范,“见名知意”

4 形参列表: 方法可以声明0个,1个,或多个形参。

​ 格式:数据类型1 形参1,数据类型2 形参2,...

5 方法体:方法功能的体现。

6.方法的使用中,可以调用当前类的属性或方法

​ 特殊的:方法A中又调用了方法A:递归方法。

递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环。

​ 方法中,不可以定义方法。

7.java中参数传递机制:值传递 与 引用传递*

如果变量是基本数据类型,此时赋值的是变量所保存的数据值。(方法中对此值做了修改,对方法外的数据无影响

如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值。(方法中对此值做了修改,对方法外的数据有影响

类的结构之三:构造器

构造器的作用:

​ 1.创建对象

​ 2.初始化对象的信息

使用说明:

​ 1.如果没显式的定义类的构造器的话,则系统默认提供一个空参的构造器

​ 2.定义构造器的格式:权限修饰符 类名(形参列表){ }

​ 3.一个类中定义的多个构造器,彼此构成重载

​ 4.一旦我们显式的定义了类的构造器之后,系统就不再提供默认的空参构造器

​ 5.一个类中,至少会有一个构造器

关键字:this

this理解为:当前对象或当前正在创建的对象(谁调用,this指的就是谁)

可以修饰、调用:属性、方法、构造器

1.this修饰属性和方法

​ 在类的方法中,我们可以使用"this.属性"或"this.方法"的方式,调用当前对象属性或方法。

​ 通常情况下,可以省略"this.";但是如果方法的形参和类的属性同名时,必须使用"this. 变量"的方式,表明此变量是属性,而非形参。

​ 在类的构造器中,我们可以使用"this.属性"或'"this.方法"的方式,调用当前正在创建的对象属性、方法

​ 通常情况下,可以省略"this.";但是如果构造器的形参和类的属性同名时,必须使用"this.变量"的方式,表明此变量是属性,而非形参。

2.this调用构造器

​ 类的构造器中,可以显式的使用"this(形参列表)"方式,调用本类中指定的其他构造器(必须要写在第一行,通过形参区分重载的方法)

​ 规定:"this(形参列表)"必须声明在当前构造器的首行

​ 构造器内部,最多只能声明一个"this(形参列表)",用来调用其他的构造器

​ 构造器中不能通过"this(形参列表)"方式调用自己

​ 如果一个类中有n个构造器,则最多有n-1构造器中使用了"this(形参列表)"

关键字:package/import

1.使用package声明当前类所在的包

​ 跨包访问的前提是,被访问的资源它的权限修饰符必须是 > 缺省

​ 1.使用 package声明类或接口所属的包,声明在源文件的首行

​ 2.一个源文件只能有一个声明包的语句。

​ 3.包名要遵守命名规则与规范:

​ 所有的单词都小写,每个单子之间使用 . 链接(每“.”一次,就代表一层文件目录)

​ 习惯使用公司的域名倒置,例如:com.atguigu.xxx

​ 补充:同一个包下,不能命名同名的接口、类;不同的包下,可以命名同名的接囗、类

2.使用import导入包里指定的类

​ import com.aaa.bbb.PackageDemo;

如果要跨包调用另一个包里的类,需要写完整的包名,书写起来很繁琐。推荐使用import 关键字导入指定的类

com.aaa.bbb.PackageDemo pd = new com.aaa.bbb.PackageDemo();

分类组织管理众多的类,例如

​ java.lang----包含一些Java语言的核心类,如String、Math、Integer、 System和Thread等,提供常用功能

​ java.net----包含执行与网络相关的操作的类和接口

​ java.io ----包含能提供多种输入/输出功能的类

​ java.util----包含一些实用工具类,如集合框架类、日期时间、数组工具类Arrays,文本扫描仪Scanner,随机值产生工具Random。

​ java.text----包含了一些java格式化相关的类

​ java.sql和javax.sql----包含了java进行JDBC数据库编程的相关类/接口

​ java.awt和java.swing----包含了构成抽象窗口工具集(abstract window toolkits)的多个类,

​ 这些类被用来构建和管理应用程序的图形用户界面(GUI)。

面向对象的特征二:继承性

1.为什么要有类的继承性?

  • 减少了代码的冗余,提高了代码的复用性
  • 便于功能的扩展
  • 为之后多态性的使用,提供了前提

2.继承性的格式:

class A extends B{}

​ A:子类、派生类、subclass

​ B:父类、超类、基类、superclass

3.子类继承父类以后有哪些不同

体现:一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。

  • 特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。只因为封装性的影响,使得子类不能直接调用父类的结构而已(可以通过getter、setter调用)

  • 子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。

    子类和父类的关系,不同于子集和集合的关系。

    extends:延展、扩展

4.子类的成员变量和成员方法的特点

​ 1.子类继承自父类以后,可以使用父类的属性,调用父类的方法

​ 权限修饰符: 子类可以访问父类的 public / protected / default 属性和方法

​ 2.父类中的成员,无论是公有还是私有,均会被子类继承。

​ 父类private修饰的方法和声明的属性,子类只能通过继承的公有方法(getter、setter...)进行访问。

​ 3.如果出现子类和父类同名成员变量的情况,优先访问子类。

​ 如果想要在子类里访问父类的成员变量,需要使用 super.成员变量名

​ 4.如果一个类有一个属性,基于它的所有子类,可以访问到。

​ 5.如果子类出现了一个和父类同名的方法,可能出现重载或者重写。

​ 子类重写父类的方法 的注意事项(详见下)

5.Java中继承性的说明

​ 1.一个类可以被多个子类继承

​ 2.Java中类的单继承性:一个类只能有一个父类

​ 3.子父类是相对的概念

​ 4.子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类

​ 5.子类继承父类以后,就获取了直接父类以及所间接父类中声明的属性和方法

6.java.lang.Object类的理解

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

方法的重写

1.什么是方法的重写(override 或 overwrite)?

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

2.应用:

​ 重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。

3.如果子类存在和父类同名的方法

​ 名字相同,但是参数不同,可以认为是一个重载

​ 名字相同,参数也相同,定义了一个和父类一样的方法

​ 子类重写了父类的方法,子类的方法会覆盖父类的方法

4.注意点:

约定俗称:子类中的叫重写的方法,父类中的叫被重写的方法

​ ① 子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同

​ ② 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符

​ 特殊情况:子类不能重写父类中声明为private权限的方法

​ ③ 返回值类型:

​ 父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类

​ 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void

​ 父类被重写的方法的返回值类型是基本数据类型(比如:int),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是int)

​ ④ 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型(具体放到异常处理时候讲)

​ 子类和父类中的同名同参数的方法要么都声明为非static的,即为重写;

​ 要么都声明为static的(不是重写),static方法为每个类的独有方法

重载和重写的区别

从编译和运行的角度看:

重载

​ 1.是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。

​ 对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。

​ 2.Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。

​ 所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定”

重写

​ 在多态中方法重写时,只有等到方法调用的那一刻,编译器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”

关键字:super

只能用在子类对象里,表示访问父类的成员和方法。(表示指向被初始化的父类空间)

​ 调用属性和方法

​ 1.我们可以在子类的方法或构造器中。通过使用" super.属性"或" super.方法"的方式,

​ 显式的调用父类中声明的属性或方法。通常情况下省略" super."

​ 2.特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,

​ 必须显式的使用" super.属性"的方式,表明调用的是父类中声明的属性。

​ 3.特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,

​ 必须显式的使用" super.方法"的方式,表明调用的是父类中被重写的方法。(重载的方法因为形参不同,在编译时就已经能确定)

​ 调用构造器

​ 1.可以在子类的构造器中显式的使用" super(形参列表)"的方式,调用父类中声明的指定的构造器

​ 2." super(形参列表)"的使用,必须声明在子类构造器的首行

​ 3.我们在类的构造器中,针对于"this(形参列表)"或" super(形参列表)"只能二选一,不能同时出现

​ 4.在构造器的首行,没有显式的声明"this(形参列表)"或" super(形参列表)",则默认调用的是父类中空参的构造器

​ 5.在类的多个构造器中,至少有一个类的构造器中使用了" super(形参列表)",调用父类中的构造器

构造方法与继承

1.构造方法不能被继承!

2.子类对象在创建时,会先自动调用父类的构造方法,继承父类属性和方法

​ 目的在于子类对象中包含了其对应的父类空间,便可以继承其父类的成员

3.如果父类没有空参数的构造方法,子类也不要有空参数的构造方法(如果有会报错)

​ 父类没有空参数的构造方法,子类必须要使用super手动调用父类指定的构造方法,不能再使用this.

​ 强烈不推荐,因为无法传参(直接把父类中的属性拿过来了)

4.子类的构造方法,应该是基于父类构造方法的扩展

子类对象实例化全过程(初始化全过程)

顺序:由父及子,静态先行

先加载类,再进行类(对象)的初始化

​ 第一步:类的初始化(类模板加载时执行一次,再创建实例对象不会反复执行)

​ 1.类的加载顺序:由父及子

​ 2.先给静态变量设置默认值

​ 3.加载静态代码块和静态变量赋值(只赋值一次)

​ 第二步:对象的初始化:

​ 1.给本类成员变量设置默认值

​ 2.调用本类指定的构造方法,只是调用,没有执行里面的代码(先执行外面的属性赋值和构造代码块)(有父类时下图)

这里可以这样理解:调用有参构造方法,本质也是先给成员变量进行赋值操作,(无参构造那就是给成员变量赋予默认值)

而构造代码块其实和构造器起同样的作用,只是它可以根据自己所在位置决定何时执行(和成员变量谁在上,谁先执行)

​ 3.(离开构造方法)给对象的成员变量赋值和执行构造代码块(谁在上谁先执行)

​ 4.执行构造方法里剩下的代码

​ 有父类时:因为子类继承父类,会先自动调用父类的构造方法,继承父类属性和方法

​ 1.给父类成员变量设置默认值

​ 2.调用父类指定的构造方法,只是调用,没有执行里面的代码

​ 3.(离开构造方法)给父类对象的成员变量赋值和执行构造代码块(谁在上谁先执行)

​ 4.执行父类构造方法里剩下的代码

image-20220208214959281

类初始化(类模板加载时执行一次,再创建实例对象不会反复执行)

​ 父类静态变量初始化

​ 父类静态构造代码块初始化 (谁在上谁先执行)

​ 子类静态变量初始化

​ 子类静态构造代码块初始化

对象的初始化

​ 父类变量初始化(初始化非静态成员变量(赋予默认值))

​ 父类构造代码块初始化(谁在上谁先执行)

​ 父类构造函数(构造函数中赋值(变量用户值))

​ 子类变量初始化

​ 子类构造代码块初始化

​ 子类构造函数

Object类的使用(详见JavaSE笔记07)

1.Object类是所Java类的根父类

2.如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类

3.Object类中的功能(属性、方法)就具通用性

​ 属性:无

​ 方法:equals()、toString()、getClass()、hashCode()、clone()、finalize()、wait()、notify()、notifyAll()

4.Object类只声明了一个空参的构造器

包装类的使用

为了使基本数据类型的变量具有类的特征(调用方法,访问属性),引入包装类

JDK 5.0 新特性:基本类型与包装类的装箱、拆箱动作可以自动完成

自动拆装箱原理:自动装箱都是通过包装类的valueOf()方法来实现的.自动拆箱都是通过包装类对象的xxxValue()来实现的。

public static  void main(String[]args){
    Integer integer = 1; //装箱
    int i = integer; //拆箱
}

javap -c 反编译后

public static  void main(String[]args){
    Integer integer=Integer.valueOf(1); 
    int i=integer.intValue(); 
}

image-20220303072744328

基本数据类型 包装类 String 间的转化:

​ 基本数据类型<--->包装类:JDK 5.0 新特性:自动装箱 与自动拆箱

​ 基本数据类型、包装类--->String:调用String重载的valueOf(Xxx xxx)

​ String--->基本数据类型、包装类:调用包装类的parseXxx(String s)

​ 注意:转换时,可能会报NumberFormatException

应用场景举例:

​ Vector类中关于添加元素,只定义了形参为Object类型的方法:

​ v.addElement(Object obj); //基本数据类型 --->包装类 --->使用多态 所以入参可以直接为8中基本数据类型

面向对象的特征三:多态性

1.多态的概念:

​ 同一个事物,有多种不同的形态.

​ 代码层面: 父类的引用指向了一个子类实例对象(运行时行为)

多态的前提是继承或者实现

2.多态的作用:

​ 意义:子类重写(实现)父类(接口)的方法;不同的子类对象,调用相同的父类方法,执行不同的(子类的)代码

父类引用指向子类对象  父类 a = new 子类();

多态的成员变量和成员方法的特点

​ 1.成员方法的特点: 调用方法时编译看左边(父类声明方法),运行看右边(看子类是否重写父类方法)

​ 编译时父类看是否有此方法,父类没有则或者看父类的父类/接口是否有 ,都没有会报错,除非强转父类的类型;

​ 运行看右边(看子类是否重写父类方法)

​ 2.成员变量的特点:编译和运行都看左边(左边父类强转为子类后,可以访问到子类的属性)

​ 对象的多态性只适用于方法,不适用于属性;属性只会被继承,不会被重写

3.引用数据类型转换(子转父、父转子)

(前提是多态,即左右两边是父子类)

向下类型转换:父类强转为子类,可以访问子类属性和方法

​ 特点:可以调用子类方法(强转成不想要的子类会报错ClassCastException)

​ 为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验

A instanceof 数据类型

A是指向子类对象的父类指引名,本质还是new的子类对象,可以用来判断是不是子类对象

如果变量属于该数据类型,返回true;如果变量不属于该数据类型,返回false。

向上类型转换

​ 编译时就是父类,不能再调用子类特有的方法,访问属性会找到父类的属性,运行时子类方法重写父类方法

​ 父类类型 变量名 = new 子类类型();

如:Animal a = new Cat();

Other

关键字:static

1.可以用来修饰的结构:主要用来修饰类的内部结构

​ 属性、方法、代码块、内部类

2.static修饰属性:静态变量(或类变量)

​ 2.1 属性,是否使用static修饰,分为:静态属性 vs 非静态属性(实例变量)

​ 实例变量:创建了类的多个对象,每个对象都独立拥有一套类中的非静态属性。

​ 当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。

​ 静态变量:创建了类的多个对象,多个对象共享同一个静态变量。

​ 当通过某一个对象修改静态变量时,其他对象再次调用的,就是被修改过了的静态变量。

​ 2.2 static修饰属性的其他说明:

 ① 静态变量随着类的加载而加载。可以通过"类.静态变量"的方式进行调用(只加载一次,即第一次new对象之前被加载到内存中)

 ② 静态变量的加载要早于对象的创建。

 ③ 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中。

​ 2.3 静态属性举例:System.out; Math.PI;

3.static修饰方法:静态方法、类方法

① 随着类的加载而加载,可以通过"类.静态方法"的方式进行调用

② 静态方法中,只能调用静态的方法或属性

​ 非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性

4.static的注意点:

​ 在静态的方法内,不能使用this关键字、super关键字

​ 关于静态属性和静态方法的使用,大家都从生命周期的角度去理解。

5.如何判定属性和方法应该使用static关键字:

5.1 关于属性

属性是可以被多个对象所共享的,不会随着对象的不同而不同的。

类中的常量也常常声明为static

5.2 关于方法

操作静态属性的方法,通常设置为static的

工具类中的方法,习惯上声明为static的。 比如:Math、Arrays、Collections

main()的使用说明

image-20220303080244045

类的结构:代码块

java里代码块分为4种:

1.普通代码块: 方法体,流程控制语句,方法体里{}

​ 方法里的代码块用来分割作用域(很少使用)

2.构造代码块: 在类里,方法外的代码块

每个构造器里都要执行的代码可以写在构造代码块里

每创建一个对象都执行一次

优先于构造方法执行,与成员变量赋值谁在上谁先执行

3.静态代码块: 和构造代码块的位置一样,就是在构造代码块的前面加了 static(只执行一次,即类加载时执行)

4.同步代码块: synchronize修饰代码块,用于多线程(被同步代码块包住的方法被加了锁)

构造代码块:

1.内部可以输出语句

2.随着对象的创建而执行

3.每创建一个对象,就执行一次非静态代码块

4.作用:可以在创建对象时,对对象的属性等进行初始化

5.如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行

6.非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法

静态代码块

1.在类里方法外,构造代码块的前加 static 修饰符

2.作用:初始化类的信息,内部可以输出语句

3.随着类的加载而执行,且只执行一次,创建多个对象不会多次执行。

4.静态代码块和静态变量的优先级相同,按照书写的先后顺序执行。

5.在静态代码块和静态变量的赋值之前,还会将静态变量设置默认值

6.在子类静态代码块以及静态变量执行之前,会先执行父类的静态代码块和静态变量的赋值。

7.静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构

关键字:final

1.可以用来修饰:类、方法、变量

2.具体的使用

final 用来修饰一个类:此类不能被其他类所继承

​ 比如:String类、System类、StringBuilder类、StringBuffer类

final 用来修饰方法:表明此方法不可以被子类覆盖重写

​ 比如:Object类中getClass();

final 用来修饰变量:此时"变量"就称为是一个常量,保存在常量区,只能被赋值一次,不允许修改

​ (通常全大写,如果有多个单词使用 _ 连接)

​ 1.final修饰属性:可以考虑赋值的位置:显式初始化、构造代码块中初始化、构造器中初始化

​ 2.final修饰局部变量或形参:

​ 使用final修饰局部变量或形参时,表明此局部变量或形参是一个常量。

​ 3.final修饰形参:

​ 调用此方法时,一旦赋值以后,就只能在方法体内使用,但不能进行重新赋值。

一般情况下final修饰的参数是在方法中不能够被修改的,但是这样的定义是不准确的,

首先如果形参类型是基本的数据类型的话参数的值是不能够被改变的,

但是如果参数类型是引用数据类型的话引用是不能够变得,但是引用的对象是可以改变的(可以改变其内部属性等);

static final 用来修饰属性:全局常量

关键字:abstract

定义:包含抽象方法的类就是抽象类

1.可以用来修饰:类、方法

2.具体的:

​ abstract修饰类:抽象类 abstract class 类名字 {public abstract void 方法名();}

​ 抽象类不能实例化

​ 抽象类不能直接创建(new)对象,如果一定要创建,需要实现(重写)抽象方法(其实是创建了匿名子类)

​ 抽象类 对象名 = new 抽象类(){ 重写方法;}

​ 抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)

​ 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作 --->抽象的使用前提:继承性

​ abstract修饰方法:抽象方法

​ 抽象方法只方法的声明,没方法体

​ 包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的(不建议这样做)

​ 若子类重写了父类中的所的抽象方法后,此子类方可实例化

​ 若子类没重写父类中所有的抽象方法,那么这个子类也是一个抽象类,需要使用abstract修饰

3.注意点:

1.abstract不能用来修饰:属性、构造器等结构

2.abstract不能用来修饰私有方法、静态方法、final的方法、final的类

关键字:interface

接口不是类(可以理解为特殊的抽象类),接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守

接口的内部主要就是封装了方法,包含抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8),私有方法(JDK 9)

1.接口使用interface来定义

2.Java中,接口和类是并列的两个结构

3.如何定义接口:定义接口中的成员

​ 1.JDK7及以前:只能定义全局常量和抽象方法

​ 全局常量:public static final的,

[public] [static] [final] int TEST_NUM =10; 可以简写成int TEST_NUM =10;

​ 抽象方法:public abstract的,

[public] [abstract] void tese(); 可以简写成void test();

​ 2.JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法

​ [public] default void demo(){ 方法体 }; 可以简写成default void demo(){ 方法体 };

4.接口中不能定义构造器的!意味着接口不可以实例化

5.Java开发中,接口通过让类去实现(implements)的方式来使用.

​ 如果实现类覆盖了接口中的所抽象方法,则此实现类就可以实例化

​ 如果实现类没覆盖接口中所的抽象方法,则此实现类仍为一个抽象类

6.Java类可以实现多个接口 --->弥补了Java单继承性的局限性(接口不能实现接口)

​ 格式:class AA extends BB implements CC,DD,EE(先写extends,后写implements)

7.接口与接口之间可以多继承 interface A extends B,C{} //A,B,C必须都是接口

8.接口的具体使用,体现多态性

9.接口,实际上可以看做是一种规范

接口注意事项:

没有构造方法,不能创建对象,没有静态代码块

不能定义私有抽象方法 private abstract

可以用接口创建对象调用方法(不可以调用静态方法),但是创建对象需要实现抽象方法(本质是创建匿名实现类的对象)

接口的5种成员介绍

1.成员常量

​ 默认修饰符是[public] [static] [final] int a =10; 可以简写成int a =10;

​ 可以直接通过 接口名.常量名 访问

2.抽象方法

​ 默认修饰符是[public] [abstract] void tese();可以简写成void test();

​ 可以通过实现类的对象,调用接口中的默认方法;

如果实现类重写了接口中的抽象方法,调用时,调用的是重写以后的方法

3.默认方法 (JDK8以后)(不能是抽象方法,必须有方法体)

​ [public] default void demo(){}; 可以简写成default void demo(){};

1.为什么要出现默认方法:

当一个接口添加新方法时,需要所有的实现类都重写新方法,影响到了已有的实现类,

可能导致应用崩溃;因此为了避免这种问题的出现我们可以使用默认方法来解决。

2.默认方法的特点:

默认方法可以被实现类继承;

默认方法可以被子类重写,也可以不重写;

子接口中如有同名默认方法,父接口中的默认方法会被覆盖

3.如何使用默认方法(default)

当一个实现类实现了多个接口,多个接口里都有相同的默认方法时,实现类必须重写该默认方法,否则编译错误

​ (类优先:如果实现类的父类中也有同名方法时,优先继承父类的默认方法,调用时使用父类的默认方法)

调用形式:对象名.默认方法名()

4.静态方法(JDK8以后)

​ [public] static void demo(){}; 可以简写成static void demo(){}

​ 只能通过 接口名.方法名 调用,不能创建(new)对象调用

​ 静态方法不允许被重写(接口中的静态方法不能被子接口继承)

(即使子类有一样的静态方法,也与接口里的静态方法没有关联,没有被继承过来)

​ 普通类的静态方法也不能被重写,即使有也各自独立,互不影响

5.私有方法(JDK9以后)出现的前提是有默认方法(private只能本类使用,只有默认方法能调用他

为什么要出现私有方法:解决多个默认方法之间的重复代码问题

在接口中我们需要抽取一个公共方法,用来解决两个默认方法之间重复代码 的问题,

但是这个共有方法不应该让实现类使用,应该是私有化的。因此要解决这个问题就要考虑使用私有方法。

1.普通私有方法是解决多个默认方法之间重复代码的问题;private void method();

2.静态私有方法是解决多个静态方法之间的代码重复问题。 private static void method();

类的结构:内部类

成员变量的特点:

​ 1.成员变量定义在类中,方法外

​ 2.通过对象名.成员变量名来操作

​ 3.每个实例对象都会单独保存一份成员属性

​ 4.成员变量随着对象的创建而创建,销毁而销毁

​ 5.成员变量都有默认值

数据类型 类型细分 默认值
基本数据类型 整数(byte,short,int,long) 0
浮点数(float,double) 0.0
字符(char) '\u0000'
布尔(boolean) false
引用数据类型 数组,类,接口 null

使用关键字new ,通过类创建实例对象

通过 对象名.属性名 可以访问到对象的属性(如果对象的属性没有设置值,它会使用默认值)

单例设计模式

单例模式,是一种常用的软件设计模式,在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中应用该模式的类一个类只有一个实例,即一个类只有一个对象实例。例如,windows操作系统里的回收站。

步骤:

  1. 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
  2. 在该类内部产生一个唯一的实例化对象,并且将其封装为private static final类型。
  3. 定义一个静态方法返回这个唯一对象。

单例设计模式分为饿汉式(立即加载型)和懒汉式(延迟加载型)。

由于懒汉式有线程安全问题,又衍生出了线程安全版的懒汉式以及DLC双检查锁(最佳实现方式)的懒汉式。

饿汉式

立即加载就是使用类的时候已经将对象创建完毕(不管以后会不会使用到该实例化对象,先创建了再说。很着急的样子,故又被称为“饿汉模式”),常见的实现办法就是直接new实例化。

public class Singleton {

    // 将自身实例化对象设置为一个属性,并用static修饰
    private static final Singleton instance = new Singleton();

    // 构造方法私有化
    private Singleton() {}

    // 静态方法返回该实例
    public static Singleton getInstance() {
        return instance;
    }
}
  • 优点:实现起来简单,没有多线程同步问题。
  • 缺点:当类SingletonTest被加载的时候,会初始化static的instance,静态变量被创建并分配内存空间,从这以后,这个static的instance对象便一直占着这段内存(即便你还没有用到这个实例),当类被卸载时,静态变量被摧毁,并释放所占有的内存,因此在某些特定条件下会耗费内存。

懒汉式

延迟加载就是调用get()方法时实例才被创建(先不急着实例化出对象,等要用的时候才给你创建出来。不着急,故又称为“懒汉模式”),常见的实现方法就是在get方法中进行new实例化。

public class Singleton {

    // 将自身实例化对象设置为一个属性,并用static修饰
    private static Singleton instance;

    // 构造方法私有化
    private Singleton() {}

    // 静态方法返回该实例
    public static Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
  • 优点:实现起来比较简单,当类SingletonTest被加载的时候,静态变量static的instance未被创建并分配内存空间,当getInstance方法第一次被调用时,初始化instance变量,并分配内存,因此在某些特定条件下会节约了内存。
  • 缺点:在多线程环境中,这种实现方法是完全错误的,根本不能保证单例的状态。

补充:线程安全的懒汉式

第三种(DCL双重检查锁)写法效率最高,强烈建议

1.使用同步方法,声明为synchronized:静态同步方法的锁是当前类本身Bank.class

class Bank{
    private Bank(){ }
    private static Bank instance=null;

    public static synchronized Bank getInstance(){
        if(instance==null){
            //说明之前没有被创建
            instance=new Bank();
        }
        return instance;
    }
}

2.使用同步代码块:

class Bank{
    private Bank(){}
    private static Bank instance=null;

    public static  Bank getInstance(){
        synchronized (Bank.class) {
            if(instance==null){
                //说明之前没有被创建
                instance=new Bank();
            }
            return instance;
        }
    }
}

3.DCL双重检查锁

本质上第一个进入的线程执行完之后对象就创建好了,那么其他的线程只要不是第一个线程就可以直接拿对象走了,

因为对象已经造好了,没有必要等了,所以可以把同步代码块包小一点,换句话说可以不把return instance看成是对共享数据的操作

最外层的if因为没有加同步所以都能判断,在同步代码块开始的地方就开始抢锁,

假设线程一抢到了,new完之后返回对象,已经来到同步代码块头处的线程是需要等线程1的。

但如果是再后来的线程,他们去判断if就已经不是null了,所以直接return而不会到同步代码块头等着了

class Bank{
    private Bank(){ }
    private static volatile Bank instance=null;
    //volatile关键字是为了禁止编译器对 volatile关键字修饰的变量进行重排序,并保证volatile变量的读操作发生在写操作之后
    public static  Bank getInstance(){

        if(instance==null) {
            synchronized (Bank.class) {
                if (instance == null) {//注意,不能把里面的if删掉
                    //说明之前没有被创建
                    instance = new Bank();
                }
            }
        }
        return instance;
    }
}

volatile关键字详见多线程笔记

posted on 2022-03-06 21:43  freyhe  阅读(42)  评论(0编辑  收藏  举报