(一)面向对象的概述
面向对象编程的思想:
面向对象编程思想实际上是一种运用对象、类、继承、封装、聚合、关联、消息、多态性等概念来构造系统的软件开发方法。
- 面向对象的分析(OOA,Object Oriented Analysis)
分析系统中涉及的事务,根据“对象”抽象出“类”;
找出对象共有的特点,并且在类中定义为属性;
找出对象共有的行为,并且定义为类的方法。 - 面向对象的设计(OOD,Object Oriented Design)
设计出这几个类型的对象,然后相互通信、传递消息完成系统功能。 - 面向对象的编程实现(OOP,Object Oriented Programming)
编写代码
编程思想的对比
| 面向过程 | 面向对象 |
|---|---|
| 系统以过程/方法为中心来组织 | 系统以对象为中心来组织 |
| 过程间相互发送数据 | 对象间相互发送消息 |
| 过程的执行动作与数据被明显分离 | 相关的属性和行为被统一到一个对象上 |
| 关注的焦点在于数据结构、算法和执行步骤 | 关注的焦点是对象及对象的职责 |
| 过程通常难以被复用 | 系统构建更容易,易维护,易扩展,易复用 |
从面向过程到面向对象,不仅仅是编程方式的改变,更重要的是思考方式的改变。
面向对象概念
- 抽象(abstract)
从事物中舍弃个别的非本质特征,抽取共同的本质特征
只考虑与问题域相关的信息,而忽略与问题域不相关的部分 - 类(class)
是一组具有相同属性和行为的对象的抽象,,类的作用是用来创建对象,对象是类的一个实例 - 对象(object)
是系统中用来描述客观事物的一个实体。
可以是有形的,也可以是无形的(如一个客户,一张银行卡,窗体中的一个按钮等等)
对象是构成世界 的一个独立单位
具有自己的静态结构(属性)和动态行为(方法)
每个对象有自己的唯一标识
类和对象的关系
类是创建对象的模板,对象是类的实例
类——是对某一类事物的描述
对象——是实际存在的某类事物的个体
创建对象的原理
Java程序是交由JVM执行的,运行时数据区通常包括这几个部分:程序计数器(Program Counter Register)、Java栈(VM Stack)、本地方法栈(Native Method Stack)、方法区(Method Area)、堆(Heap)。
我们在实例化一个对象时,同时操作了栈内存和堆内存。
在栈内保存对象的首地址,即引用;
在堆内存中保存了对象的属性。
对对象的所有操作只能通过引用完成,一旦引用出栈释放没有任何引用指向该对象,对象就变成垃圾失效。
对象创建和使用需注意:
-
编写的类不会占内存
-
只有主函数的类才能运行
-
无论多少个类,只有主函数中创建对象,才会占用内存
-
在类内的方法中,访问本类的属性和方法可以直接访问
-
在其他类中,使用对象名.(点运算符)调用属性和方法
引用类的属性:对象名.属性
引用类的方法:对象名.方法名()
静态:static关键字
- static修饰属性:
在类被载入时创建,只要类存在,static变量就存在。
被所有对象共享,当且仅当在类初次加载时会被初始化,称为静态变量或类变量。
不用static修饰的属性称为实例变量 - static修饰方法:
不需要实例化,可以直接访问,称为静态方法或类方法
访问方式:类名.方法名()(建议使用) - static修饰语句块:
类中由static关键字修饰的,不包含在任何方法体中的代码块,称为静态代码块
当类被载入时,静态代码块被执行,且只被执行一次
静态块经常用来进行类属性的初始化
注意: - 静态方法不能修饰构造函数
- 静态方法中不能使用this关键字
- 静态方法里只能直接访问静态属性和方法,而不能直接访问类中的非静态属性和方法
单例模式
单例模式(singleton):保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例模式要点:
- 某个类只能有一个实例;
- 它必须自行创建这个示例;
- 必须自行向整个系统提供这个实例。
单例模式实现:
1、拥有一个私有构造器;
2、提供一个自身静态私有的成员变量;
3、提供一个公有的静态公有的方法。
垃圾回收机制
垃圾回收(Garbage Collection,GC),JDK7以后使用G1(Garbage First)机制:
- 当没有对象引用指向原先分配给某个对象的内存时,该内存便成为垃圾。JVM的一个系统级线程会自动释放该内存块。
- 垃圾回收机制作用于堆内存,与栈内存无关。
- 在Java中创建对象占用的空间不需要编写代码进行释放空间。,自动进行垃圾回收。
对象会被回收情况:
1、对象的引用被赋值为null: Person p = new Person( ); p = null;
2、使用的匿名对象: new Person( ).sayHello( );
3、超出生命周期的,如:
for( int i = 0; i< 100; i++){
Person p = new Person( );
}
这里,创建了100个对象,循环赋值给变量p,每结束一次循环,变量p就超出生命周期,对象变为垃圾。
(二)面向对象高级特性
继承
类和类的继承(inheritance):
继承也称泛化,继承性是子类自动共享父类属性和方法的机制,在定义和实现一个类的时候,可以在一个已经存在的类的基础之上来进行,把这个已经存在的类所定义的内容作为自己的内容,并加入自己若干新的内容
- 通过关键字extends继承一个已有的类,这就是类的继承(泛化)。
- 被继承的类称为父类(超类,基类),新的类称为子类(派生类)。
[修饰符] class 子类名 extends 父类名
继承的规则:
- Java中只支持单继承,也就是说每个类只能有一个父类,不允许有多重继承
- 一个父类可以有多个子类
- 子类继承父类所有的属性和方法,同时也可以增加自己的属性和方法。
子类实例化的过程:
- 子类实例化时先实例化其父类,然后实例化子类。
- 要先调用父类的构造器,父类构造器运行完毕,才调用子类的构造器。
final关键字
final可以修饰的元素:
- 类:不能被继承
- 变量(属性和局部变量):不能被重新赋值
在声明时赋值,或在构造器中赋值
系统不会对final属性默认的赋初始值 - 方法:不能在子类中被覆盖,即不能修改。
super() 作用:
调用父类的构造器
只能出现在子类的构造器中,且必须是第一行
super()中的参数,决定了调用父类哪个构造器
如果子类构造器中没有出现super,那么编译器会默认加上super(),即调用父类的空构造器,如果父类没有空构造器,编译器提示错误。
this() 作用:
调用本类的构造器
只能写在构造器的第一行
接口和接口的继承
- 通过关键字extends继承一个已有的接口
- 接口可以实现多继承
[修饰符] interface 接口名 extends 接口1,接口2{
接口的成员
}

类和接口的继承
类实现接口 — implements
- 为了使用一个接口,你要编写实现接口的类
- 如果一个类要实现一个接口,那么这个类就必须实现接口中所有抽象方法。否则这个类只能声明为抽象类
- 多个无关的类可以实现一个接口,一个类可以实现多个无关的接口
- 一个类可以在继承一个父类的同时,实现一个或多个接口
接口可以实现多继承:
- 用接口可以实现混合类型(主类型,副类型),在Java 中可以通过接口分出主次类型
- 主类型使用继承,副类型使用接口实现
- 接口可以使方法的定义和实现相分离,降低模块间或系统间的耦合性
[修饰符] class 类名 extends 类名 implements 接口1,接口2{
类的成员
}

封装
封装(encapsulation):
所谓封装是把对象的属性和行为结合在一个独立的系统单位内部
尽可能隐蔽对象的内部细节,只向外部提供接口
降低对象间的耦合度
封装的重要意义:
- 使对象能够集中而完整地描述并对应一个具体事物
- 体现了事物的相对独立性,使对象外部不能随意存取对象的内部数据
使用访问权限修饰符对类的成员进行控制,在Java中称为“封装”。
用来控制类的成员和类的使用范围
- 构造器和类的权限通常为public,可以在所有其他类中访问;
- private权限最小,限制类外访问,一般把属性设为private,让其他类不能直接访问属性,达到保护属性的目的;
- 不使用权限修饰符时(即default)的成员在类内以及在同一个包中的其他类可以访问;
- protected所修饰的成员在类内、同一个包中、所在类的子类中都可以访问。

从大到小排列为:
public > protected > default > private
封装的常规使用
- 将类中的属性用private关键字进行隐藏
- 通过规定方法访问属性,一般通过set开头的共有方法对属性赋值,用get开头的共有方法得到属性的值
- 在方法中加入控制语句,对不合法数据进行检查
抽象类
使用abstract关键字定义抽象方法
抽象方法
- 不能被实例化,只有方法声明,没有方法实现的方法
- 抽象方法必须在其子类中被实现,否则子类只能声明为abstract,因此不能用private、final修饰
- 抽象方法不能为static;
[访问权限修饰符] abstract 返回值类型 抽象方法名 (参数列表) ;
抽象类是抽象方法和非抽象方法的集合,包含特殊情况如下:
- 抽象类中可以全部是抽象方法
- 抽象类中可以全部为非抽象方法
在下列情况下,一个类必须声明为抽象类:
- 当一个类的一个或多个方法是抽象方法时;
- 当类是一个抽象类的子类,并且没有实现父类的所有抽象方法,即只实现部分;
- 当一个类实现一个接口,并且不能为全部抽象方法都提供实现时;
接口
接口的意义 :
Java继承时一个类只有一个直接父类,也就是单继承,但是一个类可以实现多个接口,接口弥补了类的不能多继承缺点,继承和接口的双重设计既保持了类的数据安全也变相实现了多继承。
接口的概念 :
- 接口使用interface关键字来定义
- 接口中只包含常量和抽象方法,而没有变量和方法的实现
- 接口对类来说是一套规范,是一套行为协议
- 接口不是一个类,没有构造器,不能实例化
- 接口默认:
常量:public static final
抽象方法: public abstract
[访问权限修饰符] interface 接口名 {
接口的成员
}
JDK8接口默认方法
- 在JDK8之前,接口不能定义任何实现,这意味着之前所有的JAVA版本中,接口制定的方法是抽象的,不包含方法体。
- 从JKD8开始,添加了一种新功能-默认方法。默认方法允许接口方法定义默认实现,而所有子类都将拥有该方法及实现。
[访问权限修饰符] interface 接口名 {
default 方法返回值 方法名(形参){
方法体;
}
}
默认方法的优势:
- 提供一种拓展接口的方法,而不破坏现有代码。
在JDK8以前,如果为一个使用的接口增加一个新方法,必须在所有实现类中添加该方法的实现。
JDK8以后的默认方法则解决了这个问题,它提供了一个实现,当没有显示提供其他实现时就采用这个实现。这样新添加的方法将不会破坏现有代码。 - 默认方法是可选的,子类可以根据不同的需求Override默认实现。
接口与抽象类的对比
- 接口不能含有任何非抽象方法,而抽象类可以。
从JKD8开始,接口可以有默认方法。 - 类可以实现多个接口,但只能有一个父类。
- 接口和接口之间可以多继承
- 抽象类可以理解为抽象方法和非抽象方法的混合体,而接口中的方法完全是抽象方法,是一套纯粹的规范。一般来说,有关系的类才能继承同一个抽象类,而无关的类不可能有同一个抽象父类,但是无关的类可以实现同一个接口。
多态
多态(动态绑定、polymorphism):
多态是具有表现多种形态的能力的特征
同一个实现接口,使用不同的实现类而执行不同操作
不同的对象对同一行为作出的不同响应
多态存在的三个必要条件
- 要有继承,或实现
- 要有重写
- 父类引用指向子类对象
一旦满足以上3个条件,当调用父类中被重写的方法后,运行时创建的是哪个子类的对象,就调用该子类中重写的那个方法
在执行期间(而非编译期间)判断所引用对象的实际类型,根据其实际类型调用相应的方法
子类构建父类是允许的 因为子类包含父类
父类构建子类是不允许的 因为子类扩展了,范围大于父类
多态练习题
Cola公司的雇员分为以下若干类:
- ColaEmployee :这是所有员工总的父类,属性:员工的姓名,员工的生日月份。方法:getSalary(int month) 根据参数月份来确定工资,如果该月员工过生日,则公司会额外奖励100 元
public class ColaEmployee {
protected String name;
protected int birth_month;
public ColaEmployee(String name, int birth_month) {
this.name = name;
this.birth_month = birth_month;
}
public float getSalary(int month) {
if (birth_month==month) {
return 100;
}else {
return 0;
}
}
}
- SalariedEmployee:ColaEmployee 的子类,拿固定工资的员工。属性:月薪
public class SalariedEmployee extends ColaEmployee {
private float salary;
public SalariedEmployee(String name, int birth_month, float salary) {
super(name, birth_month);
this.salary = salary;
}
public float getSalary(int month) {
return salary+=super.getSalary(month);
}
}
- HourlyEmployee :ColaEmployee 的子类,按小时拿工资的员工,每月工作超出160 小时的部分按照1.5 倍工资发放。属性:每小时的工资、每月工作的小时数
public class HourlyEmployee extends ColaEmployee {
private int hours;
private float hour_sal;
public HourlyEmployee(String name, int birth_month, int hours, float hour_sal) {
super(name, birth_month);
this.hours = hours;
this.hour_sal = hour_sal;
}
public float getSalary(int month) {
float sal;
if(hours<=160) {
sal= hours*hour_sal;
}else {
sal=(float) (160*hour_sal+(hours-160)*hour_sal*1.5);
}
return sal+=super.getSalary(month);
}
}
- SalesEmployee :ColaEmployee 的子类,销售人员,工资由月销售额和提成率决定。属性:月销售额、提成率
public class SalesEmployee extends ColaEmployee {
private int salenum;
private float rate;
public SalesEmployee(String name, int birth_month, int salenum, float rate) {
super(name, birth_month);
this.salenum = salenum;
this.rate = rate;
}
public float getSalary(int month) {
float sal;
sal=salenum*rate;
return sal+=super.getSalary(month);
}
}
- 定义一个类Company,在该类中写一个方法,调用该方法可以打印出某月某个员工的工资数额
public class Company {
public void showSalary(ColaEmployee obj,int month) {
float sal = obj.getSalary(month);
System.err.println(obj.name+"的工资为"+sal);
}
}
- 写一个测试类TestCompany,在main方法,把若干各种类型的员工放在一个ColaEmployee 数组里,并打印出数组中每个员工当月的工资。
public class TestCompany {
public static void main(String[] args) {
ColaEmployee colaEmployee[] = new ColaEmployee[3];
colaEmployee[0] = new SalariedEmployee("zhangww", 8, 8000);
colaEmployee[1] = new HourlyEmployee("wangzz", 5, 170, 60);
colaEmployee[2] = new SalesEmployee("liaobb", 11, 6000, 1.5f);
Company company = new Company();
for(int i=0;i<colaEmployee.length;i++) {
company.showSalary(colaEmployee[i], 8);
}
}
}
内部类
- 内部类就是定义在另一个类内部的类。
- 内部类对于同一包中的其它类来说,内部类能够隐藏起来。
[访问权限修饰符] class 类名{
[访问权限修饰符] class 类名{
内部类成员
}
外部类成员
}
注意:
-
内部类可以访问其外部类中所有的属性和方法,无需创建外部类的对象。
-
必须创建内部类的对象,否则无法从外部类访问内部类的变量和方法。
-
如果内部类中有和外部类同名的变量或方法,则内部类的变量和方法将获得比外部类的变量和方法更高的优先级。
-
不能定义static变量
-
创建内部类
public class Outer {
private int varOuter=100;
class Inner {
int varInner=200;
public void showOuter() {
//是否能够输出?
System.out.println(varOuter);
}
}
public void showInner() {
Inner i=new Inner();
System.out.println(i.varInner);
}
}
- 访问内部类
public class MyTest{
public static void main(String[] args){
//实例化外部类
Outer o = new Outer();
//实例化内部类
Outer.Inner oi = o.new Inner();
o.showInner();
oi.showOuter();
}
}

静态内部类
用static标识的内部类为静态内部类。
静态内部类特点:
- 静态内部类作为外部类的静态成员,不能访问外部类非静态成员。
- 非静态内部类只能定义非静态成员,而静态内部类可以定义静态成员和非静态成员。
静态内部类实例化:
- 使用Outer.Inner inn=new Outer.Inner()方式实例化静态内部类。
- 非静态内部类不可以使用上面的方式实例化。
创建静态内部类
public class Outer {
static class Inner {
public void show() {
System.out.println("MyInner");
}
}
}
访问静态内部类
public class MyTest{
public static void main(String[] args){
Outer.Inner inn=new Outer.Inner();
inn.show();
}
}

局部内部类
在一个类的方法体中或程序块内定义的内部类。
局部内部类特点:在方法定义的内部类中只能访问方法中的final类型的局部变量
创建局部内部类
public class Outer {
public void 考勤(final float x) {
class My_考勤 extends AbstractEmployee{
@Override
public void 打考勤() {
System.out.println("打考勤");
}
public void 扣工资() {
System.out.println("扣工资"+x);
}
}
My_考勤 my_考勤 = new My_考勤();
my_考勤.打考勤();
my_考勤.扣工资();
}
}
访问局部内部类
public class MyTest{
public static void main(String[] args){
Outer outer = new Outer();
outer.考勤(100);
}
}

匿名内部类
没有定义类名的内部类,称为匿名内部类
匿名内部类特点:
- 匿名内部类没有访问修饰符。
- new 匿名内部类,这个类首先是要存在父类或接口。
- 外部类方法形参或局部变量需要被匿名内部类使用,必须为final。
有一个父类
public abstract class AbstractEmployee {
public abstract void 打考勤();
}
创建匿名内部类
public class Outer {
public void 考勤1(AbstractEmployee ae) {
ae.打考勤();
}
public void 考勤2() {
// class A extends AbstractEmployee{
// @Override
// public void 打考勤() {
// }
// }
// Java允许下方代码块的写法 是上方代码块的改进 称为匿名内部类
考勤1(new AbstractEmployee() {
@Override
public void 打考勤() {
System.out.println("打考勤2");
}
});
}
}
访问匿名内部类
public class MyTest{
public static void main(String[] args){
Outer outer = new Outer();
outer.考勤2();
}
}

lambda语法
在JDK8以后版本,引入lambda表达式,作用:
- 当做是一个匿名方法使用;
- 允许我们将行为传到函数里。
在Java 8之前,如果想将行为传入函数,仅有的选择是使用匿名内部类。
语法包含三部分:
- 一个括号内用逗号分隔的形式参数,参数是函数式接口里面方法的参数
- 一个箭头符号:->
- 方法体,可以是表达式和代码块。
创建一个函数式接口
public interface LambdaInterface {
void show(String str);
}
使用lambda语法
public class MyTest{
public static void main(String[] args){
LambdaInterface lambda = (s) -> {
System.out.println(s);
};
lambda.show("Hello");
}
}

Lambda表达式与匿名内部类的 区别
- 匿名内部类可以为任意接口创建实例——不管有多少个抽象方法,只要匿名内部类实现了所有方法即可。
但是Lambda表达式只能为函数式接口创建实例。 - 匿名内部类可以为抽象类甚至普通类创创建实例,
但lambda表达式只能为函数式接口创建实例。 - 匿名内部类实现的抽象方法体允许调用接口中的默认方法,
但Lambda表达式的代码块不允许调用接口中的默认方法。
Lambda表达式与匿名内部类的 相同点
- Lambda表达式和匿名内部类一样,都可以直接访问"effectively
final"的局部变量,以及外部类的成员变量(包括实力变量和类变量) - Lambda表达式创建的对象和匿名内部类创建的对象一样,都可以直接调用从接口中继承的默认方法。
来源:https://blog.csdn.net/C_CORAL/article/details/99736481


