「Java教程」面向对象

面向对象是相对于面向过程而言,过程其实就是函数,对象是将函数和属性进行了封装。
Java中的面向对象(Object Oriented)是一种新兴的程序设计方法,或者是一种新的程序设计规范(paradigm),其基本思想是使用对象、类、继承、封装、多态等基本概念来进行程序设计。

1. 类与对象

1.1 类的定义

1
class 类名 {类体}

类名由多个单词组成时,要求每个单词首字母大写

1.2 成员变量的定义

1
class 类名 { 数据类型 成员变量名=初始值; ... }

成员变量名由多个单词组成时,要求第二个起每个单词首字母大写

1.3 对象的创建

1
new 类名();

当一个类定义完毕后使用new关键字创建/构造该类的对象的过程叫做类的实例化。

1.4 引用

1
2
类名 引用变量名;
Person p = new Person(); //声明person类型的引用p指向Person类型对象
1
2
引用变量名.成员变量名;
p.name = 'zhangsan';
  • 在JAVA中,使用引用数据类型声明的变量叫做引用变量,简称‘引用’。
  • 使用引用可以记录对象在堆区中存放的内存地址信息,便于下次访问。
  • 除八种基本类型之外,用类名(接口,数组)声明的变量称为引用类型变量,引用类型变量存的某个对象的地址信息,引用的功能在于访问对象。

1.5 成员方法

1
2
3
class 类名 {
返回值类型 成员方法名(形参列表){方法体;}
}
  • 返回值类型:可以是基本数据类型,也可以是引用,当方法不需要返回数据用void
  • 形参列表:数据类型 形参1, 数据类型 形参2, …

2. 构造方法和方法重载

2.1 构造方法

1
class 类名 { 构造方法名(形参列表){构造方法体;} }
  • 构造方法名与类名相同且没有返回值
  • 当使用new关键字构造对象时,会自动调用构造方法,实现成员变量的初始化工作。

2.2 默认构造方法

  • 当一个类中没有没有自定义任何构造方法时,编译器会提供一个无参的空构造方法,叫做默认/缺省构造方法。
  • 若类中出现自定义构造方法,则编译器不再提供构造方法。

2.3 方法重载(overload)

在Java中,方法名相同,参数列表不同的方法构成重载关系。

  • 体现形式:参数个数,参数顺序,参数类型。(与形参变量名和返回值无关,但最好返回值类型相同)
  • 实际意义:调用者只需要记住一个方法名就可以不同的版本,从而实现不同的效果。

2.4 this关键字

在构造方法中出现this时,this代表当前正在构造的对象;在成员方法中出现this,this代表当前正在调用的对象。

  • 使用方式:
    1. 当形参变量和成员变量同名时,在方法体中优先使用形参变量,若希望使用成员变量,则需要加上this,即this.变量名
    2. 在构造方法的的第一行,可以调用本类中的其他构造方法。

2.5 封装

面向对象的三大特征:封装,继承,多态。

  • 封装基本概念:封装就是对成员变量的数值进行密封包装处理以及合理性判断
  • 封装基本流程:
    1. 私有化成员变量(private)
    2. 提供公有的get、set方法,并在set方法体中进行合理性判断
    3. 在构方法中调用set方法进行合理值的判断

2.6 static关键字

基本概念:通常情况下成员变量隶属于对象层级,也就是每创建一个对象就会申请一块独立的内存空间来存储就会造成内存空间的浪费。

为了解决上诉问题,Java中使用static关键字修饰该成员变量表达静态的含义,此时成员变量提升到类层级,所有对象共享,随着类的加载准备就绪,与对象创建再无关。

  • static可以修饰:修饰属性 修饰方法 修饰块 修饰类(内部类)
  • 特点
    • 静态元素在类加载时就初始化,此时还没创建对象,可以通过类名直接访问
    • 静态元素存储在静态元素区,每个类有一个自己的区域,与别的类不冲突
    • 静态元素只加载一次,全部类对象及类本身共享
    • 静态元素区Carbage Collection无法管理,可以粗暴理解为常驻内存
    • 非静态成员和静态成员都可以访问静态成员
    • 静态成员不可以访问非静态成员
    • 静态元素中不可出现this或super关键字,静态元素属于类的

3. 方法的传递和递归

3.1 传参

  • 基本数据类型变量作为参数传递时,型参数值改变不会影响实参变量的数值。
  • 引用类型变量作为参数传递时,形参指向内容的改变会影响实参变量指向的内容。
  • 引用数据类型变量作为参数传递时,形参改变指向后再改变指向内容不会影响实参指向的内容。

3.2 递归的调用

  • 递归是指方法体内部调用自身
  • 必须有递归的规律和退出条件
  • 使用递归必须使得问题简单化而不是复杂化
  • 若递归影响到程序的执行性能时,则用递推取代之

4. 单例设计模式

基本概念:当一个类有且只能对外提供一个对象时,这样的类就叫作单例类,而设计单例类的思想和模式,叫做单例设计模式。

1
2
3
4
5
6
7
8
9
10
/**
* 编程实现Singleton类的封装
*/
public class Singleton{
private static Singleton sin = new Singleton();//2.提供本类的引用指向本类的对象
private Singleton(){} //1.私有化构造方法
public static Singleton getInstance(){//3.提供公有的get方法将上述成员变量的数值返回出去
return sin;
}
}
  • 实现流程:
    1. 私有化构造方法(private)
    2. 提供本类类型的引用指向本类类型对象(private static)
    3. 提供公有的get方法将上述对象return出去(public static)
    4. 实现方式:饿汉式和懒汉式,开发中推荐饿汉式。

5. 继承(extends)

  • 继承就是子类复用父类的代码,关键字extends表示类和类的继承关系
  • 使用继承可以提高代码复用性、扩展性、以及可维护性。
    1. 子类不能继承父类的构造方法和私有方法,私有成员变量可以继承但不能直接使用。
    2. 无论使用何种方式构造方式构造子类的对象都会自动调用父类的无参构造方法来初始化从父类中继承下来的成员变量,相当于在构造方法的第一行增加super()的效果。
    3. 使用继承必须满足逻辑关系:子类 is a 父类,不能滥用继承。
    4. 在Java中只能支持单继承,也就是一个一个子类只能有一个父类,但一个父类可以有多个子类。
1
2
3
4
5
6
7
8
9
10
11
class Cricle extends Shape{
int r;
Cricle(){} //编译器会加入无参的调用 super()。
Cricle(int x, int y, int r){
super(x, y); //通过super关键字调用父类的构造方法。
setR(r);
}
public void setR(int r){
this.r = r;
}
}

6. 方法的重写(Override)

概念:从父类继承下来的方法不满足子类的需求时,就需要子类中重新写一个和父类一样的方法,覆盖从父类中继承下来的版本,该方法就叫方法的重写。

原则:

  1. 要求方法名相同,参数列表相同,返回值类型相同;jdk1.5开始返回子类类型。
  2. 要求访问权限不能变小,可以相同或变大
  3. 重写的方法不能抛出更大的异常

7. 访问控制

  • public修饰的内容可以在任意位置使用,private修饰的内容只能在本类中使用,
  • 通常情况下,成员变量都使用private修饰,成员方法都使用pubic修饰
访问控制符 访问权限 本类内部 本类中的包 子类 其他包
public 共有的 Y Y Y Y
protected 保护的 Y Y Y N
不写 默认的 Y Y N N
private 私有的 Y N N N

8. 包(Package)

为了解决命名冲突问题,便于文件的管理

1
2
3
4
package 包名;
package 包名1.包名2.包名3...包名n;
/* 指定包名时应按照一定的规范,eg: 公司域名反写.项目名称.模块名称.类名 */
org.apache.commons.lang.StringUtil;

9. final关键字

  1. final关键字修饰体现该类不能被继承(防止滥用继承)。
  2. final关键字修饰方法体现在该方法不能被重新,但可以被继承(防止不经意间造成的方法重写)。
  3. final关键字修饰成员变量体现在改成员变量必须初始化且不能更改(防止不经意间造成的数据更改)。

扩展:在开发中很少单独使用static或者final单独修饰成员变量,而是使用**public static final**共同修饰成员变量来表达常量的含义,而常量的命名规范是:所有字母大写,不同单词之间下划线连接。

10. 对象的创建过程

  • 单个对象的创建过程
    1. main方法是程序的入口,若创建对象时没有指定初始值则采用默认初始化方式处理;
    2. 若声明成员变量时进行了显示初始化操作,则最终采用显示初始化的初始值处理;
    3. 执行构造块中的代码可以对成员变量进行赋值;
    4. 执行构造方法体中的代码可以对成员变量进行再次赋值;
    5. 此时对象构造完毕,继续向下执行后续的代码;
  • 子类对象的创建过程
    1. main方法是程序的入口,先加载父类的的代码再加载子类的代码;
    2. 先执行父类静态代码块,再执行子类的静态代码块;
    3. 先执行父类的构造块,再执行父类的构造方法体,此时包含的父类对象构造完毕;
    4. 先执行子类的构造块,再执行子类的构造方法体,此时子类对象构造完毕,继续向下执行后续代码。

11. 多态

  1. 语法:父类的引用指向子类的对象
1
2
3
父类类型 引用变量名 = new 子类类型();
Person pw = new Worker();
pw.show();//再编译阶段调用Person的show()方法,在运行阶段调用Worker的show()方法。
  1. 多态的效果:
    1. 父类的引用可以直接调用父类独有的方法。
    2. 父类的引用不可以直接调用子类独有的方法。
    3. 对于父类子类都有的非静态方法来说,编译阶段调用父类的,运行阶段调用子类重写后的。
    4. 对于父类子类都有的静态方法来说,只调用父类的。
  2. 多态的实际意义:屏蔽不同子类的差异性实现通用的编程,从而带来不同的结果。
  3. 多态的表现形式
    1. 多态的前提要有继承的关系
    2. 使用父类引用指向子类对象 Person p = new Teacher();//向上转型
    3. 该引用只能调用父类中定义的属性/方法
    4. 执行结果,如果调用属性:执行父类的,如果调用方法:看子类是否重写
    5. 若想要调用子类独有的成员,将身份还原回去(向下转型/造型),若需要转换的类型与真实对象类型不匹配,会产生一个运行时异常ClassCastException
  4. 引用数据类型之间的转换
    • 转换必须发生在父子类之间,否则编译报错。
    • 自动类型转换:小到大,子类型向父类型的转换,eg:Person pw = new Worker();
    • 强制类型转换:大到小,父类型向子类型转换,eg:((Worker) pw).getSalary();//将父类引用强制转换子类型调用子类方法
  5. 为了避免类型转换异常,对象进行强制类型转换时应该用instanceof判断引用变量真正指向的对象是否是要转换的目标类型。
1
2
3
4
5
6
/*语法格式:*/ 对象 instanceof 类型  //返回布尔值
if(pw instanceof Teacher){
Teacher t = (Teacher) pw;
}else{
System.out.println("转换会有异常");
}
  1. 多态的使用场合:
1
2
3
4
5
6
7
// 通过方法的参数传递形成多态。
public static void draw(Shape s){}
TestShape.draw(new Rect(1,2,3,4));

// 在方法体中直接使用多态的语法格式。
TestAbstrat ta = new SubTestAbstract();
ta.show();

12. 抽象类

  1. 基本概念
    • abstract关键字修饰的类称为抽象类。
    • 抽象类不能实例化,抽象类的意义在于被继承。
    • 抽象类为其子类“抽象”出了公共部分,通常也定义了子类所必须具体实现的抽象方法。
    • 抽象方法:指不能具体实现的方法,没有方法体并使用abstract修饰。
1
2
3
4
5
public abstract class Shape{ //一个类若定义了抽象方法,则必须以abstract关键字声明为抽象类
private int x;
private int y;
public abstract boolean contains(int x, int y);//用abstract修饰的方法,称之为抽象方法,没有方法体
}
  1. 注意:
    1. 抽象类中可以有成员变量,成员方法,以及构造方法。
    2. 抽象类中可以没有抽象方法,也可以有抽象方法。
    3. 具有抽象方法的类必须是抽象类,因此其真正意义的抽象类应该是有抽象方法,并且使用abstract修饰。
    4. 子类必须实现抽象方法(不同子类可能有不同实现),否则改子类也变抽象。
    5. 抽象类对子类具有强制性和规范性,因此叫做模板设计模式。
    6. 推荐使用多态的语法格式实现抽象类,若需要更换子类时,该方法中只需要将new关键字后面的类型名称修改而其他位置无需改变就可以立即生效,从而提高了代码的维护性和扩展性。

多态实现抽象类的缺点:若希望调用子类独有的方法时,则需要强制类型转换。

13. 接口

  1. 基本概念:接口可以看成是特殊的抽象类。即只包含抽象方法的抽象类。通过interface关键字定义。
1
2
3
4
interface Runner { //-通过interface关键字定义接口
public static final int SEF_SPEED=100;//-接口中不能定义成员变量,只能定义常量
public void run();//-接口中只可以定义没有实现的方法(可以省略public abstract)
}
  1. 一个类可以通过implements关键字实现接口,一个类可以实现多个接口,并且该类需要实现这些接口中定义的所有方法。
1
2
3
4
5
6
7
8
9
10
class American implements Runner,... { //与继承不同,可以实现多个接口
@Override
public void run(){//该类需要实现接口中定义的所有方法
System.out.println("run...");
}
public static void main(String[] args) {
Runner ra = new American();//接口作为一种类型声明,并且声明的变量可以引用实现类的对象
ra.run();//通过该变量可以调用该接口定义的方法
}
}
  1. 一个接口可以通过extends关键字继承另一个接口,子接口继承了父接口所有的方法。
1
interface Hunter extends Runner{...}
  1. 类与接口的关系
    • 类和类使用extends继承,仅支持单继承
    • 接口和接口使用extends继承,支持多继承。
    • 类使用implements实现接口,支持多实现
  2. 抽象类与接口的关系(笔试题)
    1. 定义抽象类:abstract class,而定义接口:interface
    2. 类继承抽象类:extends单继承,而类实现接口:implements多实现;
    3. 抽象类可以构造方法,而接口不能有构造方法;
    4. 抽象类可以有成员变量,而接口只能有常量
    5. 抽象类可以有成员方法,而接口只能有抽象方法
    6. 抽象类中增加方法子类可以不用重写,而接口中增加方法子类必须重写
    7. 从jdk1.8开始允许接口中有非抽象方法,但需要default关键字修饰。

14. 内部类

  • 内部类指的是在Java中可以将一个类定义在另一个类定义在另一个类的内部
  • 内部类定义在 类的内部 ,与类成员层次一致
  • 内部类定义在 方法/块内部(与类成员相差一个层次,方法的局部变量一个层次)
    • 成员内部类:将一个类直接定义在类的里面,作为成员,与属性或方法层次一致
    • 局部内部类:将一个类定义在方法/块里面,作为成员的内部结构,与临时的局部变量一个层次
    • 匿名内部类:成员匿名内部类,局部匿名内部类
    • 静态内部类:成员静态内部类

14.1 *成员内部类

  • 将一个类直接定义在类的里面,作为成员,与属性或方法层次一致
  • 成员内部类可以与正常类一样 使用不同的修饰符来修饰
  • 好处1.省略了一个.java文件 好处2.成员内部类中可以访问外部类的所有成员 包括私有的
  • 若想要在内部类中通过对象.调用外部类成员 外部类.this.外部类成员;
  • 内部类存在后 源代码进行编译 产生一个字节码 Demo$InnerDemo.class

14.2 局部内部类

  • 将一个类定义在方法/块里面,作为成员的内部结构,与临时的局部变量一个层次
  • 局部内部类像是一个局部的变量一样,不能用public protected private及static
  • 只能用abstract或final
  • 局部内部类命名规则Demo$1InnerTestMethod Demo$2InnerTestMethod
  • 局部内部类使用的变量只能是final修饰

14.3 *匿名内部类

将类直接定义在类中 或者类成员中 成员匿名内部类 局部匿名内部类

匿名内部类没有类的所有结构(名字 修饰符) 只有类体

通常会在抽象类或接口创建的后面使用,当然具体的类也可以有匿名子类

匿名类内部没有构造方法,也不能用任何修饰符来修饰

  • 当接口类型的引用作为方法的形参时,实参的传递方式有两种:
    1. 自定义类实现接口并重写抽象方法,然后创建该类的对象作为实参传递。
    2. 直接使用匿名内部类的语法格式得到接口类型的引用,再作为实参传递。
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
public interface A {
public abstract void show();
}
//-方式1:自定义类实现接口并重写抽象方法,然后创建该类的对象作为实参传递
public class SubA implements A {
@Override
public void show() {
System.out.println("这里自定义类实现接口并重写抽象方法!");
}
}
//测试类
public class ATest {
public static void test(A a) {
a.show();
}
public static void main(String[] args) {
//ATest.test(new A());//报错,A是接口,不能new对象
//-方式1:接口实现类的对象作为实参传递
ATest.test(new ASub());//接口类型引用指向实现类的对象,形成了多态。

//-方式2:匿名内部类
// 接口/父类类型 引用变量名 = new 接口/父类类型() {方法的重写};
A ta = new A() {
@Override
public void show() {
System.out.println("这里是匿名内部类");
}
};
ATest.test(ta);//得到接口类型的引用,再作为实参传递
}
}
  • 匿名内部类定义:如果在一段程序需要创建一个类的对象(通常这个类需要实现某个接口或继承某个类),而且对象创建后这个类的价值就不存在了,这个类不必命名,称之为匿名内部类。
  • 语法格式:接口/父类类型 引用变量名 = new 接口/父类类型() {匿名类类体,这里重写方法};
1
SuperType obj = new SuperType(...){ ... };

14.4 静态内部类

  • 成员静态内部类
  • 不需要外部类对象,通过正常的方式直接创建内部类
  • 静态元素不能访问非静态成员(自己类和外部类)

15. 回调模式

回调模式是指:如果一个方法的参数是接口类型,则在调用该方法时,需要创建并传递一个实现此接口的对象;而该方法在运行时会调用到参数对象中所实现的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface Action{
public void doSth();
}
//repeat方法需要一个Action接口类型参数,让其doSth方法重复执行n次
public static void repeat(int n, Action ac){
for(int i=0; i<n; i++){ ac.doSth();}
}
//此处的语义可解释为:通过接口回调传递了一个方法给repeat,让repeat将其执行5次。
public static void main(String[] args){
repeat(5, new Action(){//通过匿名内部类传递参数
public void doSth(){
System.out.println("Hello")
}
});
}

16. 抽象方法的笔试考点

  • abstract与哪些关键字不能共存:
    1. final关键字;因为final关键字修饰的类不能被继承,方法不能被重写,而abstract关键字修饰的类继承后,该类的方法需要重写,相互冲突。
    2. static关键字;因为static能被实例化可直接调用,而abstract不能被实例化,相互冲突。
    3. private关键字;因为private修饰的私有方法不能被继承,就不能重写,而abstract方法需要重写。

17. 枚举类(enum)

  • 一个类中的对象 认为个数是有限且固定的 可以将每一个对象一一列举出来
  • 创建枚举类型要使用 enum 关键字,隐含了所创建的类型都是 java.lang.Enum 类的子类(java.lang.Enum 是一个抽象类)。枚举类型符合通用模式 Class Enum<E extends Enum>,而 E 表示枚举类型的名称。枚举类型的每一个值都将映射到 protected Enum(String name, int ordinal) 构造函数中,在这里,每个值的名称都被转换成一个字符串,并且序数设置表示了此设置被创建的顺序。
  • 我们自己定义的每一个enum类型 都会默认继承Enum 间接继承Object
  • Enum类型,有两个属性
    • name—–>枚举对象的名字,name()获取name属性
    • ordinal—>枚举对象在类中罗列的顺序 类似index 也从0开始 ordinal()获取序号
  • 一些常用的方法
    • valueOf() 通过给定的name获取对应的枚举对象
    • values() 获取全部的枚举对象 —> 返回一个数组 Day[]
    • compareTo() 可以比较两个枚举对象 int
    • toString() 由于这个方法没有final修饰 可以覆盖(重写)
  • switch内部判断枚举的应用
  • 我们也可以在enum中描述自己的一些属性或方法
    • 必须在enum类中第一行 描述一下枚举的样子 最后需要分号结束;
    • 可以定义自己的属性
    • 类创建的过程中 帮我们创建枚举类型的对象
    • 需要给枚举类型提供对应样子的构造方法 构造方法只能private修饰 可以重载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public enum Day{
//描述了七个当前类的对象
monday("星期一",1),tuesday("星期二",2),wednesday,thursday,friday,saturday,sunday;

private String name;
private int index;

private Day(){}
private Day(String name,int index){
this.name=name;
this.index=index;
}

public String getName(){
return this.name;
}
public void setName(String name){
this.name=name;
}
}

18. 内存机制问题

  • 类创建在哪儿 对象创建在哪里 继承关系 静态成员 方法执行
  • 栈内存—>Person p = new Person();—->堆内存 方法区—类模板
    • 栈内存—-变量空间,方法临时执行空间(从创建开始执行完毕,立即回收
    • 堆内存—-new申请对象空间(垃圾回收器GC,对象空间没有任何引用指向视为垃圾)
    • 方法区—-常量 类模板 静态成员(有且只有一份,不回收
  • Runtime类(是单例模式)之中提供了几个管理内存的方法
    • maxMemory
    • totalMemory
    • freeMemory
    • 栈内存溢出错误StackOverflowError
    • 堆内存溢出错误OutOfMemoryError
  • Object类中有一个finalize方法 如果重写也能看见对象回收的效果
  • GC系统提供的一个线程 回收算法