Java面向对象编程
一、Java面向对象编程
1.1 类和对象
- **类(Class)和对象(Object)**是面向对象的核心概念。
- 类是对一类事物的描述,是抽象的、概念上的定义。
- 对象是实际存在的该类事物的每个个体,因而也称为实例(instance)。
- 设计类,其实就是设计类的成员。
- 属性 = 成员变量 = field = 域、字段
- 方法 = 成员方法 = 函数 = method
- 创建类的对象 = 类的实例化 = 实例化类
- 类和对象的使用(面向对象思想落地的实现)
- 创建类,设计类的成员。
- 创建类的对象。
- 通过“对象.属性”或“对象.方法”调用对象的结构。
- 如果创建了一个类的多个对象,则每个对象都独立的拥有一套类的属性。(非static的)意味着:如果我们修改一个对象的属性a,则不影响另外一个对象属性a的值。
- 类的访问机制:
- 在一个类中的访问机制:类中的方法可以直接访问类中的成员变量。(例外:static方法访问非static,编译不通过。)
- 在不同类中的访问机制:先创建要访问类的对象,再用对象访问类中定义的成员。
1.2 属性
成员变量vs局部变量
相同点:
- 定义变量的格式:数据类型 变量名 = 变量值。
- 先声明,后使用。
- 变量都有其对应的作用域。
不同点:
在类中声明的位置的不同
- 属性:直接定义在类的一对{}内。
- 局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量。
关于权限修饰符的不同
- 属性:可以在声明属性时,指明其权限,使用权限修饰符。(常用的权限修饰符:private、public、缺省、protected —>封装性)
- 局部变量:不可以使用权限修饰符。
默认初始化值的情况
- 属性:类的属性,根据其类型,都有默认初始化值。
1
2
3
4
5整型(byte、short、int、long):0
浮点型(float、double):0.0
字符型(char):0 (或'\u0000')
布尔型(boolean):false
引用数据类型(类、数组、接口):null- 局部变量:没有默认初始化值。(意味着,我们在调用局部变量之前,一定要显式赋值。特别地:形参在调用时,我们赋值即可。)
在内存中加载的位置
- 属性:加载到堆空间中(非static)。
- 局部变量:加载到栈空间。
1.3 方法
- 方法的分类:按照是否有形参及返回值。
1.3.1 方法的重载(overload)
- 在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
- 重载的特点:与返回值类型无关,只看参数列表,且参数列表必须不同。(参数个数或参数类型)。调用时,根据方法参数列表的不同来区别。
1 | public class PrintStream { |
1.3.2 可变个数的形参
- JavaSE 5.0 中提供了Varargs(variable number of arguments)机制,允许直接定义能和多个实参相匹配的形参。从而,可以用一种更简单的方式,来传递个数可变的实参。
- JDK 5.0以前:采用数组形参来定义方法,传入多个同一类型变量。
- public static void test(int a ,String[] books)
- JDK5.0:采用可变个数形参来定义方法,传入多个同一类型变量。
- public static void test(int a ,String…books)
- JDK 5.0以前:采用数组形参来定义方法,传入多个同一类型变量。
- 声明格式:方法名(参数的类型名 …参数名)
- 可变参数:方法参数部分指定类型的参数个数是可变多个:0个,1个或多个
- 可变个数形参的方法与同名的方法之间,彼此构成重载。
- 可变个数形参的方法与本类中方法名相同,形参类型也相同的数组之间不构成重载。换句话说,二者不能共存。
- 可变参数方法的使用与方法参数部分使用数组是一致的。
- 方法的参数部分有可变形参,需要放在形参声明的最后。
- 在一个方法的形参位置,最多只能声明一个可变个数形参。
1 | public class MethodArgsTest { |
1.3.3 方法参数的值传递机制
Java里方法的参数传递方式只有一种:值传递。 即将实际参数值的副本(复制品)传入方法内,而参数本身不受影响。
- 形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参。
- 形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参。
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
35public class ValueTransferTest {
public static void main(String[] args) {
System.out.println("***********基本数据类型:****************");
int m = 10;
int n = m;
System.out.println("m = " + m + ", n = " + n);//m = 10, n = 10
n = 20;
System.out.println("m = " + m + ", n = " + n);//m = 10, n = 20
System.out.println("***********引用数据类型:****************");
Order o1 = new Order();
o1.orderId = 1001;
Order o2 = o1;//赋值以后,o1和o2的地址值相同,都指向了堆空间中同一个对象实体。
System.out.println("o1.orderId = " + o1.orderId + ",o2.orderId = " +o2.orderId);//o1.orderId = 1001,o2.orderId = 1001
o2.orderId = 1002;
System.out.println("o1.orderId = " + o1.orderId + ",o2.orderId = " +o2.orderId);//o1.orderId = 1002,o2.orderId = 1002
}
}
class Order{
int orderId;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class ValueTransferTest1 {
public static void main(String[] args) {
int m = 10;
int n = 20;
System.out.println("m = " + m + ", n = " + n);//m = 10, n = 20
ValueTransferTest1 test = new ValueTransferTest1();
test.swap(m, n);
System.out.println("m = " + m + ", n = " + n);//m = 10, n = 20
}
public void swap(int m,int n){
int temp = m ;
m = n;
n = temp;
}
}图解:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public class ValueTransferTest {
public static void main(String[] args) {
Data data = new Data();
data.m = 10;
data.n = 20;
System.out.println("m = " + data.m + ", n = " + data.n);//m = 10, n = 20
ValueTransferTest2 test = new ValueTransferTest2();
test.swap(data);
System.out.println("m = " + data.m + ", n = " + data.n);//m = 20, n = 10
}
public void swap(Data data){
int temp = data.m;
data.m = data.n;
data.n = temp;
}
}
class Data{
int m;
int n;
}图解:
1.4 封装与隐藏
- 隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
- Java中通过将数据声明为私有的(private),再提供公共的(public)方法:getXxx()和setXxx()实现对该属性的操作,以实现下述目的:
- 隐藏一个类中不需要对外提供的实现细节。
- 使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑,限制对属性的不合理操作。
- 便于修改,增强代码的可维护性。
- 封装性的体现,需要权限修饰符来配合。
- Java规定的4种权限(从小到大排列):private、缺省、protected 、public 。
- 4种权限可以用来修饰类及类的内部结构:属性、方法、构造器、内部类。
- 具体的,4种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部类;修饰类的话,只能使用:缺省、public。
1.5 构造器(或构造方法)
- 构造器的特征
- 它具有与类相同的名称。
- 它不声明返回值类型。(与声明为void不同)
- 不能被static、final、synchronized、abstract、native修饰,不能有return语句返回值。
- 根据参数不同,构造器可以分为如下两类:
- 隐式无参构造器(系统默认提供)
- 显式定义一个或多个构造器(无参、有参)
- 注意:
- Java语言中,每个类都至少有一个构造器。
- 默认构造器的修饰符与所属类的修饰符一致。
- 一旦显式定义了构造器,则系统不再提供默认构造器。
- 一个类可以创建多个重载的构造器。
- 父类的构造器不可被子类继承。
1.6 JavaBean
- JavaBean是一种Java语言写成的可重用组件。
- 所谓javaBean,是指符合如下标准的Java类:
- 类是公共的。
- 有一个无参的公共的构造器。
- 有属性,且有对应的get、set方法。
1 | public class Customer { |
1.7 this的使用
- 在Java中,this关键字比较难理解,它的作用和其词义很接近。
- 它在方法内部使用,即这个方法所属对象的引用。
- 它在构造器内部使用,表示该构造器正在初始化的对象。
- this 可以调用类的属性、方法和构造器。
- 在类的方法中,我们可以使用”this.属性”或”this.方法”的方式,调用当前对象属性或方法。但是,通常情况下,我们都选择省略”this.”。特殊情况下,如果方法的形参和类的属性同名时,我们必须显式的使用”this.变量”的方式,表明此变量是属性,而非形参。
- 在类的构造器中,我们可以使用”this.属性”或”this.方法”的方式,调用当前正在创建的对象属性或方法。但是,通常情况下,我们都选择省略”this.”。特殊情况下,如果构造器的形参和类的属性同名时,我们必须显式的使用”this.变量”的方式,表明此变量是属性,而非形参。
- this调用构造器:
- 我们在类的构造器中,可以显式的使用”this(形参列表)”方式,调用本类中指定的其他构造器。
- 构造器中不能通过”this(形参列表)”方式调用自己。
- 如果一个类中有n个构造器,则最多有 n - 1构造器中使用了”this(形参列表)”。
- 规定:”this(形参列表)”必须声明在当前构造器的首行。
- 构造器内部,最多只能声明一个”this(形参列表)”,用来调用其他的构造器。
1.8 继承性
- 多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。
- 此处的多个类称为子类(派生类),单独的这个类称为父类(基类或超类)。
- 一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。只有因为封装性的影响,使得子类不能直接调用父类的结构而已。
- 子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。
- Java中关于继承性的规定:
- 一个类可以被多个子类继承。
- Java中类的单继承性:一个类只能有一个父类。
- 子父类是相对的概念。
- 子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类。
- 子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法。
- 如果我们没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类。
- 所有的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类。
- 意味着,所有的java类具有java.lang.Object类声明的功能。
- 作用:
- 继承的出现减少了代码冗余,提高了代码的复用性。
- 继承的出现,更有利于功能的扩展。
- 继承的出现让类与类之间产生了关系,提供了多态的前提。
1.9 方法的重写(override/overwrite)
- 重写:子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作。
- 子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表。
- 重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。
- 子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同。
- 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符。(特殊情况:子类不能重写父类中声明为private权限的方法)
- 返回值类型:
- 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void。
- 父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类。
- 父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)。
- 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型。
- 子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(不是重写)。
1.10 super的使用
- super调用属性和方法
- 我们可以在子类的方法或构造器中。通过使用”super.属性”或”super.方法”的方式显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略”super.”。
- 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的使用”super.属性”的方式,表明调用的是父类中声明的属性。
- 特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的使用”super.方法”的方式,表明调用的是父类中被重写的方法。
- super调用构造器
- 我们可以在子类的构造器中显式的使用”super(形参列表)”的方式,调用父类中声明的指定的构造器。
- “super(形参列表)”的使用,必须声明在子类构造器的首行!
- 我们在类的构造器中,针对于”this(形参列表)”或”super(形参列表)”只能二选一,不能同时出现。
- 在构造器的首行,没有显式的声明”this(形参列表)”或”super(形参列表)”,则默认调用的是父类中空参的构造器:super()
- 在类的多个构造器中,至少有一个类的构造器中使用了”super(形参列表)”,调用父类中的构造器。
1.11 多态性
- 对象的多态性:父类的引用指向子类的对象。
- 可以直接应用在抽象类和接口上。
- Java引用变量有两个类型:编译时类型和运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。简称:编译时,看左边;运行时,看右边。
- 若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)。
- 多态情况下,“看左边”:看的是父类的引用(父类中不具备子类特有的方法);“看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)
- 多态性的使用前提: ① 类的继承关系 ② 方法的重写
- 对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)
1 | public class Person { |
- 虚拟方法调用(多态情况下)
- 子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
1.12 instanceof 操作符
- a instanceof A:判断对象a是否是类A的实例。如果是,返回true;如果不是,返回false。
- 使用情境:为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型。如果返回false,不进行向下转型。
- 如果 a instanceof A返回true,则 a instanceof B也返回true,其中,类B是类A的父类。
1 | public class PersonTest { |
1.13 ==操作符与equals方法
== :运算符
- 可以使用在基本数据类型变量和引用数据类型变量中。
- 如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型要相同)
- 如果比较的是引用数据类型变量:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体。
equals()方法
是一个方法,而非运算符。
只能适用于引用数据类型。
Object类中equals()的定义:
1
2
3public boolean equals(Object obj) {
return (this == obj);
}- 说明:Object类中定义的equals()和==的作用是相同的:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体。
像String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的”实体内容”是否相同。
通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的”实体内容”是否相同。那么,我们就需要对Object类中的equals()进行重写。
- 重写的原则:比较两个对象的实体内容是否相同。
1 | public class EqualsTest { |
1.14 toString()方法
当我们输出一个对象的引用时,实际上就是调用当前对象的toString()。
Object类中toString()的定义:
1
2
3public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}像String、Date、File、包装类等都重写了Object类中的toString()方法。使得在调用对象的toString()时,返回”实体内容”信息。
自定义类也可以重写toString()方法,当调用此方法时,返回对象的”实体内容”。
1 | public class Customer { |
1.15 包装类的使用
- 针对八种基本数据类型定义相应的引用类型—包装类(封装类)。
- 有了类的特点,就可以调用类中的方法,Java才是真正的面向对象。
- 基本数据类型 —>包装类:调用包装类的构造器
1 |
|
- 包装类—>基本数据类型:调用包装类Xxx的xxxValue()
1 |
|
- JDK 5.0 新特性:自动装箱 与 自动拆箱
1 |
|
- 基本数据类型、包装类—>String类型:调用String重载的valueOf(Xxx xxx)
1 |
|
- String类型 —>基本数据类型、包装类:调用包装类的parseXxx(String s)
1 |
|
练习题:
1 |
|
1.16 关键字:static
- 当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份,例如所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量。
- static可以用来修饰:属性、方法、代码块、内部类。
- 使用static修饰属性:静态变量(或类变量)
- 实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。
- 静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的。
- static修饰属性的其他说明
- 静态变量随着类的加载而加载。可以通过”类.静态变量”的方式进行调用。
- 静态变量的加载要早于对象的创建。
- 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中。
- 使用static修饰方法:静态方法
- 随着类的加载而加载,可以通过”类.静态方法”的方式进行调用。
- 静态方法中,只能调用静态的方法或属性;非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性。
- 使用static修饰属性:静态变量(或类变量)
- static注意点
- 在静态的方法内,不能使用this关键字、super关键字。
- 开发中,如何确定一个属性是否要声明为static的?
- 属性是可以被多个对象所共享的,不会随着对象的不同而不同的。
- 类中的常量也常常声明为static。
- 开发中,如何确定一个方法是否要声明为static的?
- 操作静态属性的方法,通常设置为static的。
- 工具类中的方法,习惯上声明为static的。 比如:Math、Arrays、Collections。
内存解析如下图:
1.17 理解main方法
- main()方法作为程序的入口。
- main()方法也是一个普通的静态方法。
- main()方法可以作为我们与控制台交互的方式。(之前:使用Scanner)
例1:
1 | public class MainTest { |
例2:
1 | public class MainDemo { |
在idea编译器选择Edit Configurations后在Program arguments处填写几个字符串:
运行结果:
1 | *****aa |
或者使用“java MainDemo aa bb cc”输出同样的结果。
1.18 代码块
- 代码块的作用:用来初始化类、对象。
- 代码块如果有修饰的话,只能使用static。
- 代码块可分为静态代码块和非静态代码块。
- 静态代码块
- 内部可以有输出语句。
- 随着类的加载而执行,而且只执行一次。
- 作用:初始化类的信息。
- 如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行。
- 静态代码块的执行要优先于非静态代码块的执行。
- 静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构。
- 非静态代码块
- 内部可以有输出语句。
- 随着对象的创建而执行。
- 每创建一个对象,就执行一次非静态代码块。
- 作用:可以在创建对象时,对对象的属性等进行初始化。
- 如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行。
- 非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法。
- 静态代码块
例1:
1 | public class BlockTest { |
执行结果:
1 | 静态代码块1 |
对属性可以赋值的位置:
- ①默认初始化
- ②显式初始化/⑤在代码块中赋值
- ③构造器中初始化
- ④有了对象以后,可以通过”对象.属性”或”对象.方法”的方式,进行赋值
执行的先后顺序:① →② / ⑤ →③ → ④
1.19 关键字:final
- final可以用来修饰的结构:类、方法、变量。
- final 用来修饰一个类:此类不能被其他类所继承。(比如:String类、System类、StringBuffer类)
- final 用来修饰方法:表明此方法不可以被重写。(比如:Object类中getClass();)
- final 用来修饰变量:此时的”变量”就称为是一个常量。
- final修饰属性:可以考虑赋值的位置有:显式初始化、代码块中初始化、构造器中初始化。
- final修饰局部变量:尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值。
1 | public class FinalTest { |
- static final可以用来修饰属性和方法。
1.20 抽象类与抽象方法
- abstract可以用来修饰的结构:类、方法
- abstract修饰类:抽象类
- 此类不能实例化。
- 抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
- 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作。
- abstract修饰方法:抽象方法
- 抽象方法只有方法的声明,没有方法体。
- 包含抽象方法的类,一定是一个抽象类。反之,抽象类中是可以没有抽象方法的。
- 若子类重写了父类中的所有的抽象方法后,此子类方可实例化。若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰。
- abstract修饰类:抽象类
- abstract不能用来修饰:属性、构造器等结构。
- abstract不能用来修饰私有方法、静态方法、final的方法、final的类。
- 抽象类的匿名子类:
1 | public class PersonTest { |
1.21 接口(interface)
接口使用interface来定义。
Java中,接口和类是并列的两个结构。
定义接口
JDK7及以前:只能定义全局常量和抽象方法。
- 全局常量:public static final的,但是书写时,可以省略不写。
抽象方法:public abstract的。
JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法。
接口中不能定义构造器的,意味着接口不可以实例化。
Java开发中,接口通过让类去实现(implements)的方式来使用。
- 如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化。
- 如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类。
Java类可以实现多个接口—>弥补了Java单继承性的局限性。(格式:class AA extends BB implements CC,DD,EE)
接口与接口之间可以继承,而且可以多继承。
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个”是不是”的关系,而接口实现则是 “能不能”的关系。
接口使用上也满足多态性:
1 | public class USBTest { |
1 | interface A { |
- java8接口新特性(除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法)
1 | public interface CompareA { |
1.22 内部类
- 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。
- 在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。
- 内部类的分类:成员内部类(静态、非静态)和局部内部类(方法内、代码块内、构造器内)
- 成员内部类:
- 一方面,作为外部类的成员:调用外部类的结构;可以被static修饰;可以被4种不同的权限修饰。
- 另一方面,作为一个类:类内可以定义属性、方法、构造器等;可以被final修饰,表示此类不能被继承。言外之意,不使用final,就可以被继承;可以被abstract修饰。
- 成员内部类:
1 | public class InnerClassTest { |
其中,局部内部类的使用:
1 | public class InnerClassTest1 { |
1 | public class InnerClassTest { |