一、包(Package)

1、包的作用

  • 避免类重名:有了包之后,类的全名称就变为包.类名

  • 作用:控制某些类型或成员的可见范围

  • 常见包:分类组织管理众多的类

    • java.lang 包含一些Java语言的核心类,如String、Math、Integer、 System和Thread等,提供常用功能
    • java.net 包含执行与网络相关的操作的类和接口
    • java.io 包含能提供多种输入/输出功能的类
    • java.util 包含一些实用工具类,如集合框架类、日期时间、数组工具类Arrays,文本扫描仪Scanner,随机值产生工具Random
    • java.text 包含了一些java格式化相关的类
    • java.sqljavax.sql 包含了java进行JDBC数据库编程的相关类/接口
    • java.awtjava.swing 包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)

2、声明包

  • 声明包格式:package 包名;

  • 声明包注意事项

    • 必须在源文件的代码首行
    • 一个源文件只能有一个声明包的package语句
  • 包的命名规范和习惯

    • 所有单词都小写,每一个单词之间使用.分割
    • 习惯用公司的域名倒置开头(例:com.xxx.xxx)

3、跨包使用类

  • 代码中使用类型的全名称(例:java.util.Scanner

  • 使用import 语句之后,代码中使用简名称(import语句必须在package下面,class的上面)

    • import 包.类名;
    • import 包.*;
  • 使用java.lang包下的类,不需要import语句,可以直接使用简名称

注意:只有public的类才能被跨包使用

二、类和对象

  • 类:是一类具有相同特性的事物的抽象描述,是一组相关属性和行为的集合

  • 对象:是一类事物的一个具体个体,即对象是类的一个实例,必然具备该类事物的属性和行为

  • 类与对象的关系

    • 类是对一类事物的描述,是抽象的
    • 对象是一类事物的实例,是具体的
    • 类是对象的模板,对象是类的实体
  • 类的定义格式,关键字:class

1
2
3
【修饰符】 class 类名{

}
  • 对象的创建:关键字:new
1
2
new 类名()//匿名对象
类名 对象名 = new 类名();//给创建的对象命名

对象名中存储的是对象地址,指向堆中对象的首地址

1、修饰符

(1)类修饰符

  • 类只有缺省和pubilc两种修饰符

    • 缺省:只限本包访问
    • public:所有包都可以访问

(2)类成员修饰符

  • 访问权限修饰符
修饰符 本类 本包 其他包子类 其他包非子类
private × × ×
缺省 × ×
protected ×
public
  • 其他修饰符

    • static:静态成员
    • final:不可变
      • final修饰类:不能被继承
      • final修饰方法:不能被重写
      • final修饰变量:值不能被修改,即常量(常量名建议使用大写字母)
        • 被final修饰的变量没有set方法,且必须初始化
          • 成员变量:必须初始化时赋值(因为有默认值),实例变量可以在构造器中初始化赋值
          • 局部变量:可以显式赋值、或在初始化块赋值
    • native:C语言写的方法体,与底层JVM交互,方法不带大括号

(3)局部变量修饰符

  • final:局部变量值不能被修改

2、类成员

(1)成员变量

1
2
3
【修饰符】 class 类名{
【修饰符】 数据类型 成员变量名;
}
  • 可以是Java的任意类型,包括基本数据类型、引用数据类型(类、接口、数组等),默认值同数组

  • 修饰符中的static可以将成员变量分为非静态变量(实例变量)和静态变量(类变量)

静态变量(类变量) 非静态变量(实例变量)
存储位置 方法区
签名位置 方法区 方法区
对象权限 所有对象共享 每一个对象独立
生命周期 和类一致 每一个对象独立
本类访问 变量名 [this.]变量名
子类访问 不能被子类继承 super.变量名
其他类访问 import static 包.类名.变量名; import static 包.类名.变量名;
对象访问 对象.变量名 对象.变量名
  • private静态变量的get/set方法也是静态的

局部变量存储在栈中,无默认值

局部变量和成员变量重名情况下,局部变量拥有更高优先级,访问实例成员变量需要加this.

(2)成员代码块

  • 成员代码块:和构造器一样,用于变量初始化(创建对象时调用,早于构造器)
  • 修饰符中的static可以将成员代码块分为非静态代码块和静态代码块
静态代码块 非静态代码块
语法格式 static { 静态代码块 } { 普通代码块 }
作用和特点 为类变量初始化,只执行一次 为实例变量初始化
优先级

(3)成员构造器(构造方法)

  • 构造器作用:new对象时,为实例变量赋值

  • 构造器特点:只能在创建对象时调用,名称与类名一致,没有返回值

  • 语法格式

    • 无参构造:构造器名() {}
    • 有参构造:构造器名(参数列表) {}
  • 同一个类中的构造器互相调用

    • this():调用本类的无参构造
    • this(实参列表):调用本类的有参构造
    • this()和this(实参列表)只能出现在构造器首行
    • 不能出现递归调用
  • 注意事项

    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
package com.atguigu.constructor;

public class Student {
private String name;
private int age;

// 无参构造
public Student() {
// this("",18);//调用本类有参构造
}

// 有参构造
public Student(String name,int age) {
this();//调用本类无参构造
this.name = name;
this.age = age;
}

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}

public String getInfo(){
return "姓名:" + name +",年龄:" + age;
}
}

(4)成员方法(Method)

① 成员方法

1
2
3
【修饰符】 返回值类型 方法名(【形参列表 】)【throws 异常列表】{
方法体的功能代码
}
  • 方法位置:必须在类中,方法外(即不能在一个方法中直接定义另一个方法)

  • 方法的局部变量

    • 形参和方法中的变量不能重名,生命周期一样
    • 形参由方法调用点决定,方法内变量由方法内部决定
  • 关键字static可以将方法分为静态方法(类方法)和非静态方法(实例方法)

静态方法(类方法) 非静态方法(实例方法)
存储位置 方法区
签名位置 方法区 方法区
对象权限 所有对象共享 每一个对象独立
生命周期 和类一致 每一个对象独立
本类访问 方法名.() [this.]方法名(【实参列表】)
子类访问 不能被子类继承 super.方法名(【实参列表】)
其他类访问 import static 包.类名.方法(); import static 包.类名.方法();
对象访问 对象.方法名() 对象.方法名(【实参列表】)
方法重写 不可以 可以
  • 成员方法的内存分析

    • 方法调用之后入栈,即给当前方法开辟一块独立的内存区域,用于存储当前方法的局部变量的值
    • 方法执行结束后出栈,即释放内存,如果方法有返回值,就会把结果返回调用处

② 方法构成

  • 方法头(方法签名):方法头可能包含5个部分,但是有些部分是可能缺省的

    • 修饰符:可选。例如:public、protected、private、static、abstract、native、final、synchronized等
    • 返回值: 表示方法运行结果的数据类型,方法执行后将结果返回到调用者
      • 如果被调用方法的返回值类型是void,调用时不需要也不能接收和处理返回值结果
      • 如果被调用方法有返回值,即返回值类型不是void, 可以不接收和处理,但这种情况下返回值丢失
    • 方法名:见名知意,能准确代表该方法功能的名字
    • 参数列表(形参):表示完成方法体功能时需要外部提供的数据列表
      • 无论是否有参数,()不能省略
      • 如果有参数,每一个参数都要指定数据类型和参数名,多个参数之间使用,分隔
    • throws异常列表:可选
  • 方法体:方法体必须有{}括起来,在{}中编写完成方法功能的代码

    • return语句的说明
      • 如果返回值类型不是void,方法体中必须保证一定有return 返回值
      • return语句后面就不能再写其他代码了,否则会报错:Unreachable code

③ 方法参数

(1)可变参数(JDK1.5之后)

  • 可变参数作用:方便调用点调用

  • 可变参数的格式

    • 参数类型... 形参名
    • 参数类型[] 形参名)(此形式必须传递数组)
  • 可变参数的特点和要求

    • 一个方法的参数列表中,只能有一个变长参数组,可变参数必须是形参列表的最后一个
    • 在声明它的方法中,可变参数当成数组使用
    • 变长参数组可以不传参

(2)参数传递

  • 形参为基本数据类型:形参值的改变不会影响实参;
  • 形参为引用数据类型:形参地址值的改变不会影响实参,但是形参地址值里面的数据的改变会影响实参

④ 方法重载

  • 方法重载:在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可

  • 参数列表不同的定义

    • 参数个数不同
    • 数据类型不同
    • 参数顺序不同(不推荐,逻辑上容易有歧义)
  • 重载方法调用机制:JVM通过方法的参数列表,调用匹配的方法

    • 先找个数、类型最匹配的
    • 再找个数和类型可以兼容的(如果多个方法同时可以兼容将会报错)

⑤ 方法的递归调用(了解)

  • 递归调用:方法自己调用自己的现象就称为递归

  • 递归的分类:递归分为两种,直接递归和间接递归

    • 直接递归:方法自身调用自己
    • 间接递归:A方法调用B方法,B方法调用C方法,C方法调用A方法
  • 注意事项

    • 递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出
    • 在递归中虽然有限定条件,但是递归深度不能太深,否则效率低下,或者也会发生栈内存溢出
    • 能使用循环代替的,尽量使用循环代替递归

(5)内部类

  • 将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类

  • 根据内部类声明的位置(如同变量的分类),我们可以分为

    • 成员内部类
      • 静态成员内部类
      • 非静态成员内部类
    • 局部内部类
      • 有名字的局部内部类
      • 匿名的内部类

① 静态成员内部类

  • 静态内部类:如果成员内部类中不使用外部类的非静态成员,那么通常将内部类声明为静态内部类
  • 语法格式:
1
2
3
4
【修饰符】 class 外部类{
【其他修饰符】 【static】 class 内部类{
}
}
  • 静态内部类特点:

    • 可以继承任何父类和实现任何接口
    • 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名和$符号
    • 如果在内部类中有变量与外部类的静态成员变量同名,可以使用外部类名.进行区别
    • 在外部类的外面不需要通过外部类的对象就可以创建静态内部类的对象(通常应该避免这样使用)

其实严格的讲(在James Gosling等人编著的《The Java Language Specification》)静态内部类不是内部类,而是类似于C++的嵌套类的概念,外部类仅仅是静态内部类的一种命名空间的限定名形式而已。所以接口中的内部类通常都不叫内部类,因为接口中的内部成员都是隐式是静态的(即public static)。例如:Map.Entry。

② 非静态成员内部类

  • 没有static修饰的成员内部类叫做非静态内部类

  • 非静态内部类的特点:

    • 可以继承任何父类和实现任何接口,且可以继承父类的静态成员
    • 不允许声明静态变量和静态方法,但可以声明静态常量
    • 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名和$符号
    • 在外部类的外面必须通过外部类的对象才能创建非静态内部类的对象(通常应该避免这样使用)
      • 如果要在外部类的外面使用非静态内部类的对象,通常在外部类中提供一个方法来返回这个非静态内部类的对象比较合适
      • 在非静态内部类的方法中有两个this对象,一个是外部类的this对象,一个是内部类的this对象

③ 局部内部类

  • 语法格式:
1
2
3
4
5
6
【修饰符】 class 外部类{
【修饰符】 返回值类型 方法名(【形参列表】){
final/abstract】 class 内部类{
}
}
}
  • 局部内部类的特点:

    • 可以继承任何父类和实现任何接口
    • 不允许声明静态变量和静态方法,但可以声明静态常量
    • 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名、$符号、编号
      • 这里有编号是因为同一个外部类中,不同的方法中存在相同名称的局部内部类

JDK1.8之后,如果某个局部变量在局部内部类中被使用了,自动加final(考虑到生命周期)

④ 匿名内部类

  • 如果有些子类或实现类是一次性的,那么可以使用匿名内部类的方式来实现,避免给类命名的问题
  • 格式:
1
2
3
new 父类/父接口(【实参列表】){
重写方法...
}

匿名内部类是一种特殊的局部内部类,所有局部内部类的限制都适用于匿名内部类

  • 使用匿名内部类的对象直接调用方法
1
2
3
4
5
6
7
8
9
10
11
12
13
interface A{
void a();
}
public class Test{
public static void main(String[] args){
new A(){
@Override
public void a() {
System.out.println("aaaa");
}
}.a();
}
}
  • 通过父类或父接口的变量多态引用匿名内部类的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface A{
void a();
}
public class Test{
public static void main(String[] args){
A obj = new A(){
@Override
public void a() {
System.out.println("aaaa");
}
};
obj.a();
}
}
  • 匿名内部类的对象作为实参
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface A{
void method();
}
public class Test{
public static void test(A a){
a.method();
}

public static void main(String[] args){
test(new A(){

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

三、初始化顺序

1、类的初始化

类的初始化:也是静态变量初始化,编译器会调用自动生成的类初始化方法,按顺序合并到方法体中

    • 类初始化代码只执行一次
    • 父类初始化优先于子类初始化
    • 类的初始化优先于实例初始化
    • 静态属性初始化优先于静态代码块

2、实例初始化

实例初始化:也是实例变量初始化,创建对象后,按顺序合并到方法体中

    • 实例属性优先于普通代码块,普通代码块优先于构造器

四、抽象类

  • 抽象类:被abstract修饰的类

  • 抽象类的语法格式:【权限修饰符】 abstract class 类名;

  • 抽象方法:被abstract修饰没有方法体的方法

  • 抽象方法的语法格式:【其他修饰符】 abstract 返回值类型 方法名(【形参列表】);

  • 注意事项

    • 方法实现(重写):抽象类的子类,必须重写抽象父类中所有的抽象方法,除非该子类也是抽象类
    • 创建对象:抽象类不能创建对象,只能创建其非抽象子类的对象
    • 构造方法:抽象类中,也有构造方法,是供子类创建对象时,初始化父类成员变量使用的
    • 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类

五、包装类

  • Java提供了两个类型系统,基本类型与引用类型,使用基本类型在于效率,然而当要使用只针对对象设计的API或新特性(例如泛型),那么基本数据类型的数据就需要用包装类来包装
基本数据类型 包装类(java.lang包)
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean
  • 除此之外还有方法中的void返回值,对应的包装类为Void

1、装箱与拆箱

  • 装箱:把基本数据类型转为包装类对象(为了使用专门为对象设计的API和特性)

    • 格式(int类型为例):包装类对象.valueof(基本数据类型)
  • 拆箱:把包装类对象拆为基本数据类型(一般是因为需要运算)

    • 格式(int类型为例):包装类对象.intvalue()
  • JDK1.5之后,可以自动装箱与拆箱,但只能与自己对应的类型之间才能实现自动装箱与拆箱

1
2
3
Integer i = 4;//自动装箱。相当于Integer i = Integer.valueOf(4);
i = i + 5;//等号右边:将i对象转成基本数值(自动拆箱) i.intValue() + 5;
//加法运算完成后,再次装箱,把基本数值转成对象

2、包装类的一些API

(1)基本数据类型和字符串之间的转换

  • 基本数据类型转为字符串

    • 方式一:基础数据类型 + "";
    • 方式二:String.valueOf(基础数据类型);
  • 字符串转为基本数据类型

    • 方式一:除了Character类之外,其他所有包装类都具有parseXxx静态方法可以将字符串参数转换为对应的基本类型
      • public static int parseInt(String s):将字符串参数转换为对应的int基本类型
      • public static long parseLong(String s):将字符串参数转换为对应的long基本类型
      • public static double parseDouble(String s):将字符串参数转换为对应的double基本类型
    • 方式二:把字符串转为包装类,然后可以自动拆箱为基本数据类型
      • public static Integer valueOf(String s):将字符串参数转换为对应的Integer包装类,然后可以自动拆箱为int基本类型
      • public static Long valueOf(String s):将字符串参数转换为对应的Long包装类,然后可以自动拆箱为long基本类型
      • public static Double valueOf(String s):将字符串参数转换为对应的Double包装类,然后可以自动拆箱为double基本类型

如果字符串参数的内容无法正确转换为对应的基本类型,则会抛出java.lang.NumberFormatException异常

(2)数据类型的最大最小值

  • Integer.MAX_VALUEInteger.MIN_VALUE
  • Long.MAX_VALUELong.MIN_VALUE
  • Double.MAX_VALUEDouble.MIN_VALUE

(3)字符转大小写

  • Character.toUpperCase('x');
  • Character.toLowerCase('X');

(4)整数转进制

  • Integer.toBinaryString(int i);
  • Integer.toHexString(int i);
  • Integer.toOctalString(int i);

(5)比较的方法

  • Double.compare(double d1, double d2);
  • Integer.compare(int x, int y);

3、包装类对象的特点

(1)包装类缓存对象

  • 缓存对象:在方法区的整数型常量池
包装类 缓存对象
Byte -128~127
Short -128~127
Integer -128~127
Long -128~127
Float 没有
Double 没有
Character 0~127
Boolean true和false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Integer a = 1;
Integer b = 1;
System.out.println(a == b);//true

Integer i = 128;
Integer j = 128;
System.out.println(i == j);//false

Integer m = new Integer(1);//新new的在堆中
Integer n = 1;//这个用的是缓冲的常量对象,在方法区
System.out.println(m == n);//false

Integer x = new Integer(1);//新new的在堆中
Integer y = new Integer(1);//另一个新new的在堆中
System.out.println(x == y);//false

(2)类型转换问题

  • 基本数据类型和对应包装类:自动拆箱后按基本数据类型比较
1
2
3
Integer i = 1000;
int j = 1000;
System.out.println(i==j);//true
  • 基本数据类型和其他包装类:自动拆箱后,按照基本数据类型的自动转换规则转换后再比较,可能会报错
1
2
3
Integer i = 1000;
double j = 1000;
System.out.println(i==j);//true
  • 包装类和其他包装类:不能比较
1
2
3
Integer i = 1;
Double d = 1.0
System.out.println(i==d);//编译报错

(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
public class TestExam {
public static void main(String[] args) {
int i = 1;
Integer j = new Integer(2);
Circle c = new Circle();
change(i,j,c);
System.out.println("i = " + i);//1
System.out.println("j = " + j);//2
System.out.println("c.radius = " + c.radius);//10.0
}

/*
* 方法的参数传递机制:
* (1)基本数据类型:形参的修改完全不影响实参
* (2)引用数据类型:通过形参修改对象的属性值,会影响实参的属性值
* 这类Integer等包装类对象是“不可变”对象,即一旦修改,就是新对象,和实参就无关了
*/
public static void change(int a ,Integer b,Circle c ){
a += 10;
// b += 10;//等价于 b = new Integer(b+10);
c.radius += 10;
/*c = new Circle();
c.radius+=10;*/
}
}
class Circle{
double radius;
}

六、封装

1、封装概述

  • 封装:控制类或成员的可见性范围,从而提高系统的可扩展性、可维护性

  • 面向对象的开发原则要遵循“高内聚、低耦合”

    • 高内聚:类的内部数据操作细节自己完成,不允许外部干涉
    • 低耦合:仅对外暴露少量的方法用于使用
  • 实现封装:访问控制修饰符,也称为权限修饰符

修饰符 本类 本包 其他包子类 其他包非子类
private × × ×
缺省 × ×
protected ×
public

2、成员变量私有化

  • 成员变量(field)私有化之后,提供标准的get/set方法,这种成员变量也称为属性(property)

  • 封装目的

    • 隐藏类的实现细节
    • 限制对成员变量的不合理访问
    • 可以进行数据检查,从而有利于保证对象信息的完整性
    • 便于修改,提高代码的可维护性
  • 实现步骤

    1. 使用 private 修饰成员变量:private 数据类型 变量名;
    2. 提供 getXxx方法 / setXxx 方法,可以访问成员变量
1
2
3
4
5
6
7
8
9
10
public class Person {
private String name;

public void setName(String n) {
name = n;
}

public String getName() {
return name;
}

IDEA自动生成get/set方法模板

  • 大部分键盘模式按Alt + Insert键
  • 部分键盘模式需要按Alt + Insert + Fn键
  • Mac电脑快捷键需要单独设置

七、继承

1、继承概述

  • Java中的继承:多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类中无需再定义这些属性和行为,只需要和抽取出来的类构成某种关系。其中,多个类可以称为子类,也叫派生类;多个类抽取出来的这个类称为父类、超类(superclass)或者基类

  • 继承描述的是事物之间的所属关系,这种关系是:is-a 的关系

  • 继承的优点

    • 提高代码的复用性
    • 提高代码的扩展性
  • 继承的特点

    • 子类会继承父类所有的实例变量和实例方法(不继承构造器)
      • 当子类对象被创建时,在堆中给子类和父类声明的实例变量分配内存
      • 当子类对象调用方法时,编译器遵循从下往上的顺序查找是否声明了此方法,未找到则会报编译错误
    • Java只支持单继承,不支持多重继承
    • Java支持多层继承
    • 一个父类可以同时拥有多个子类
    • 子类可以继承父类private的成员变量,但子类不能直接进行访问,可通过继承的get/set方法访问
  • 继承权限修饰符

    • 类权限修饰符:public、缺省
    • 类成员权限修饰符:public、protected、缺省、private

跨包使用时,如果类的权限修饰符缺省,即使成员权限修饰符>类的权限修饰符也不可以访问

  • 继承状态下的构造器调用

    • 默认子类构造器调用父类无参构造器
    • 子类构造器必须在第一行调用父类构造器
  • 继承状态下调用父类:super.xxx()(super关键字代指父类对象,且只能向上一级)

  • 继承的语法格式:【修饰符】 class 子类 extends 父类

2、方法重写(Override)

  • 方法重写:必须发生在父子类,方法名和参数必须一样,子类重新编写父类方法签名(同一个方法),建议返回类型、异常一模一样

  • 方法重写注解:@Override

    • IDEA重写方法快捷键:Ctrl + o / alt+insert
  • 重写方法的要求

    • 必须保证父子类之间重写方法的名称相同
    • 必须保证父子类之间重写方法的参数列表也完全相同
    • 返回值类型:子类方法的返回值类型必须【小于等于】父类方法的返回值类型(如果返回值类型是基本数据类型和void,那么必须是相同)
    • 异常值类型:子类抛出的异常类型必须【小于等于】父类抛出的异常类型
    • 权限修饰符:子类方法的权限必须【大于等于】父类方法的权限修饰符(父类private方法不能重写)

3、Object根父类

  • java.lang.Object是类层次结构的根类,即所有类的父类,每个类都使用 Object 作为超类
  • 如果一个类没有特别指定父类,那么默认则继承自Object类
  • Object类型的变量引用非Object的任意引用数据类型对象都是多态引用
  • 所有对象(包括数组)都可以实现Object类的方法

通过查询API的方式,可以知道如何使用Java提供的类,如果要查看具体实现代码,则需要查看src源码

  • Object类中包含的方法有11个,以下5个较为常用

    • toString(),方法签名:public String toString()
      • 默认情况下,toString()返回的是对象的运行时类型@对象的hashCode值的十六进制形式,建议重写
      • System.out.println(对象),默认会自动调用这个对象的toString()
    • getClass(),方法签名:public final Class<?> getClass(),获取对象的运行时类型
    • equals(),方法签名:public boolean equals(Object obj),用于判断当前对象this与指定对象obj是否相等
      • 默认情况下,equals方法的实现等价于与==,比较的是对象的地址值,建议重写
      • 如果重写equals,需要遵循以下原则
        • 自反性:x.equals(x)返回true
        • 传递性:x.equals(y)为true, y.equals(z)为truex.equals(z)也应为true
        • 一致性:只要参与equals比较的属性值没有修改,那么无论何时调用结果应该一致
        • 对称性:x.equals(y)y.equals(x)结果应该一样
        • 非空对象与nullequals一定是false
    • hashCode(),方法签名public int hashCode(),返回每个对象的hash值,主要是为了提高对象存储和查询性能,通常会与equals()方法一起重写,因为关于hashCode有两个常规协定
      • ① 如果两个对象的hash值是不同的,那么这两个对象一定不相等
      • ② 如果两个对象的hash值是相同的,那么这两个对象不一定相等
    • finalize(),方法签名:protected void finalize():用于最终清理内存的方法,定时启动,回收没有引用指向的对象
      • 当对象被GC确定为要被回收的垃圾,回收之前由GC自动调用这个方法
      • finalize方法的调用不一定会销毁当前对象,因为可能在finalize()中出现了让当前对象“复活”的代码
      • 每一个对象的finalize方法只会被调用一次
      • 子类可以选择重写,一般用于彻底释放一些资源对象,而且这些资源对象往往是通过C/C++等代码申请的资源内存

八、多态

1、多态概述

  • 多态:任何一个需要父类引用的位置可以传递一个子类对象
  • 多态的三个关键步骤
    1. 必须有继承
    2. 父类定义方法,子类重写方法
    3. 父类的引用指向子类的对象

本态:本态的引用本类的对象,本态优先级更高

  • 多态引用的表现:编译时类型与运行时类型不一致,引用调用方法时必须看引用数据类型,方法运行时体现对象的方法

    • 编译时类型:编译时按照左边变量的类型处理,就只能调用父类中有的变量和方法,不能调用子类特有的变量和方法了
    • 运行时类型:运行时按照右边对象本身的类型处理,所以执行的方法是子类重写的方法体
  • 多态引用的好处和弊端

    • 弊端:编译时看父类,只能调用父类声明的方法,不能调用子类扩展的方法
    • 好处:运行时看子类,如果子类重写了方法,一定是执行子类重写的方法体,实现动态绑定

2、多态引用形式

  • 声明变量是父类类型,变量赋值子类对象

    • 方法的形参是父类类型,调用方法的实参是子类对象
    • 实例变量声明父类类型,实际存储的是子类对象
  • 数组元素是父类类型,元素对象是子类对象

  • 方法返回值类型声明为父类类型,实际返回的是子类对象

3、向上转型与向下转型

  • 向上转型:左边是父类,右边是子类,就称为向上转型,是自动完成的

  • 向下转型:左边是子类,右边是父类,就称为向下转型,可通过语法完成:(子类类型)父类变量

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

    • 语法:变量/匿名对象 instanceof 数据类型
    • 变量/匿名对象的编译时类型与instanceof后面数据类型是直系亲属关系才可以比较
    • 变量/匿名对象的运行时类型<=instanceof后面数据类型,才为true

4、虚方法(实例方法)

  • 虚方法(实例方法):可重写、多态的
  • 调用虚方法的执行原理
    1. 静态分派:先看编译时类型,在这个对象的编译时类型中找到能匹配的方法
    2. 动态绑定:再看运行时类型,如果运行时类型重写了刚刚找到的那个匹配的方法,那么执行重写的方法,否则仍然执行刚才编译时类型中匹配的方法

四、标准JavaBean

JavaBean 是 Java语言编写类的一种标准规范。符合JavaBean 的类,要求:

(1)类必须是具体的和公共的

(2)具有无参数的构造方法

(3)成员变量私有化,并提供用来操作成员变量的setget 方法

(4)重写toString方法