面向对象

谁说我不能喝 提交于 2019-11-28 01:00:07

(一)面向对象的概述

面向对象编程的思想:

面向对象编程思想实际上是一种运用对象、类、继承、封装、聚合、关联、消息、多态性等概念来构造系统的软件开发方法。

  • 面向对象的分析(OOA,Object Oriented Analysis)
    分析系统中涉及的事务,根据“对象”抽象出“类”;
    找出对象共有的特点,并且在类中定义为属性;
    找出对象共有的行为,并且定义为类的方法。
  • 面向对象的设计(OOD,Object Oriented Design)
    设计出这几个类型的对象,然后相互通信、传递消息完成系统功能。
  • 面向对象的编程实现(OOP,Object Oriented Programming)
    编写代码

编程思想的对比

面向过程 面向对象
系统以过程/方法为中心来组织 系统以对象为中心来组织
过程间相互发送数据 对象间相互发送消息
过程的执行动作与数据被明显分离 相关的属性和行为被统一到一个对象上
关注的焦点在于数据结构、算法和执行步骤 关注的焦点是对象及对象的职责
过程通常难以被复用 系统构建更容易,易维护,易扩展,易复用

从面向过程到面向对象,不仅仅是编程方式的改变,更重要的是思考方式的改变。

面向对象概念

  1. 抽象(abstract)
    从事物中舍弃个别的非本质特征,抽取共同的本质特征
    只考虑与问题域相关的信息,而忽略与问题域不相关的部分
  2. 类(class)
    是一组具有相同属性和行为的对象的抽象,,类的作用是用来创建对象,对象是类的一个实例
  3. 对象(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  父类名

继承的规则:

  1. Java中只支持单继承,也就是说每个类只能有一个父类,不允许有多重继承
  2. 一个父类可以有多个子类
  3. 子类继承父类所有的属性和方法,同时也可以增加自己的属性和方法。

子类实例化的过程:

  1. 子类实例化时先实例化其父类,然后实例化子类。
  2. 要先调用父类的构造器,父类构造器运行完毕,才调用子类的构造器。

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):
多态是具有表现多种形态的能力的特征
同一个实现接口,使用不同的实现类而执行不同操作
不同的对象对同一行为作出的不同响应

多态存在的三个必要条件

  1. 要有继承,或实现
  2. 要有重写
  3. 父类引用指向子类对象

一旦满足以上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表达式与匿名内部类的 区别

  1. 匿名内部类可以为任意接口创建实例——不管有多少个抽象方法,只要匿名内部类实现了所有方法即可。
    但是Lambda表达式只能为函数式接口创建实例。
  2. 匿名内部类可以为抽象类甚至普通类创创建实例,
    但lambda表达式只能为函数式接口创建实例。
  3. 匿名内部类实现的抽象方法体允许调用接口中的默认方法,
    但Lambda表达式的代码块不允许调用接口中的默认方法。

Lambda表达式与匿名内部类的 相同点

  1. Lambda表达式和匿名内部类一样,都可以直接访问"effectively
    final"的局部变量,以及外部类的成员变量(包括实力变量和类变量)
  2. Lambda表达式创建的对象和匿名内部类创建的对象一样,都可以直接调用从接口中继承的默认方法。
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!