首页 > 编程语言 >第5章:面向对象编程(中)

第5章:面向对象编程(中)

时间:2023-01-29 09:45:21浏览次数:53  
标签:重写 调用 对象 子类 面向对象编程 父类 方法

第5章:面向对象编程(中)

1、OOP特征二:继承性(inheritance)

1.1继承性的作用(好处):

(1)减少了代码的冗余,提高了代码的复用性。

(2)便于功能的扩展

(3)为之后多态性的使用,提供了前提。

注:不能为了获取其他类的某个功能而去继承,要复合逻辑道理。

1.2继承性的格式:

1.2.1 格式:class A extends B{}

A:子类或派生类(subclass)

B:父类或超类或基类(superclass)

1.3继承的特点:

1.3.1 一旦子类A继承父类B以后,子类A就获取到了父类B中声明的所有结构(属性、方法)。特别的,父类中声明为private(私有的)的属性或方法,子类继承父类以后,仍然认为获取到了父类中的私有的结构,只是因为封装性的影响,使得该私有结构只能被父类调用,而不能被子类调用而已。

1.3.2 子类继承父类以后,还可以声明自己特有的属性或方法,从而实现功能的拓展(extends)。

1.4继承的规定:

(1)一个类(父类)可以被多个子类继承。

(2)单继承性(C++中可以多重继承,即一个类有多个父类):一个类(子类)只能有一个父类。

(3)子父类是个相对的概念。那么类可以多层继承(多层继承关系)。

(4)子类直接继承的父类称为:直接父类;间接继承的父类称为:间接父类(某一个子类的父类所继承的所有类,包括其父类的父类等)

(5)子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法。

1.5 object类:

(1)如果我们没有显示声明一个类的父类的话,则此类就继承与java.lang.object类。

(2)所有的类都直接或间接地继承java.lang.object类。

(3)所有的java类都具有java.lang.object类声明的功能。

2、方法的重写(override/overwrite)

2.1 定义:

​ 在子类中可以根据需要对从父类中继承来的同名同参数的方法进行改造,也称为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。

2.2 应用:

​ 重写以后,当创建子类对象以后,通过子类对象调用父类中的同名同参数的方法时,实际执行的是子类重写的父类的方法。

2.3 重写的规定:

方法的声明: 权限修饰符 返回值类型 方法名(形参列表)throws 异常类型

约定:子类中的叫重写的方法,父类中的叫被重写的方法。

(1)子类重写的方法的方法名和形参列表与父类被重写的方法的方法名形参列表相同。(方法名和形参列表唯一确定某一个方法)

(2)子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符。(特殊情况:子类不能重写父类中声明为private权限的方法,只能说在子类中新写了一个方法,因为其看不到父类中的private方法)

(3)返回值类型:

父类被重写的方法的返回值是void,则子类重写的方法的返回值类型只能是void。

父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类。

父类被重写的方法的返回值类型是基本数据类型,则子类重写的方法的返回值类型必须是相同的基本数据类型。

(4)子类重写的方法抛出的异常类型不大于父类被重写的异常类型。


子类和父类中的同名同参数的方法要么都声明为非static的(可以考虑重写),要么声明为static的(不能重写)。

3、四种访问权限修饰符

4、关键字:super

4.1 理解: super是父类的
4.2 修饰对象: 属性、方法、构造器
4.3 super的使用:调用属性(1、2)和方法(3)

(1)我们可以在子类的方法或构造器中,使用“super.属性“或”super.方法“的方式,显示的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略“super.”

(2)特殊情况,当子类和父类中定义了同名的属性(不像方法一样会重写覆盖,子类和父类的同名方法同时存在于内存中),我们想要在子类中调用父类中声明的属性,则必须显式地使用“super.属性”的方式,表明调用的是父类中声明的属性。其默认调用的是子类中的属性,即“this.属性”。

(3)当子类重写了父类的方法后,我们想要在子类中调用父类中被重写的方法时,需要显式地用“super.方法”的方式,表明调用的是父类中被重写的方法。如果子类中没有重写父类的方法,而直接使用父类的方法,则该方法默认前面是super,即调用父类的方法。

4.4 super的使用:调用构造器

(1)我们可以在子类的构造器中显示的使用“super(形参列表)”的方式,调用父类中声明的指定的构造器。

(2)“super(形参列表)”的使用,必须声明在子类构造的首行。

(3)我们在类的构造器中,针对于“this(形参列表)”或“super(形参列表)”只能二选一,不能同时出现。

(4)在构造器的首行,既没有显示地声明“this(形参列表)”或“super(形参列表)”,则默认调用的是父类中空参的构造器,即“super(形参列表)”;

(5)在类的多个构造器中,至少有一个类的构造器中使用了“super(形参列表)”来调用父类中的构造器。(与n个构造器中,有n - 1个构造器是使用“this(形参列表)”)

5、子类对象实例化过程

5.1 从结果上来看:(即继承性的体现)

子类继承父类以后,就获取了父类中声明的属性和方法。

创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。

5.2 从过程上来看:

当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的……的构造器,直到调用了java.lang.object的空参的构造器位置为止。正因为构造器,我们加载过所有父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用。

注:

1、虽然创建子类对象时,调用了父类的构造器,但自始至终就创建过一个对象,即为new的子类对象。

2、加载的顺序是从父类开始到自身结束的,即先加载super的构造器,再是加载自己的构造器中的内容。

6、OOP特征三:多态性

6.1 理解:

一个事物的多种形态。

6.2 什么是多态性:

对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)

格式:父类 变量名 = new 子类();

6.3 多态的使用:(即虚拟方法调用)

虚拟方法调用:当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法。

有了对象的多态性以后,我们在编译期间,只能调用父类中声明的结构,但在运行期间,我们实际执行的是子类重写父类的方法。(编译看左边,执行看右边)

6.4 多态性使用前提:

(1)要有类的继承关系

(2)父类要定义该方法,各个子类中要有该方法的重写

6.5 应用

对于某个方法,我们只需要写入其父类,然后通过传入其子类作为形参,从而能够根据不同的子类,获得所对应的重写方法。提高了代码的通用性,常用来接口的重用。

注:对象多态性,只适用于方法(编译看左边,查看引用变量所声明的类中是否有所调用的方法,运行看右边,调用实际new的对象所属的类中的重写方法),不适用于属性(编译运行都看左边,属性还是父类的)。多态是运行时的行为,成员方法具备多态性,成员变量不具备多态性,只看引用变量所声明的类。

6.6 虚拟方法调用(Virtual Method Invocation)说明:

正常的方法调用:

例子:

Person e = new Person(); //对象实例化

e.getInfo(); //调用方法

虚拟方法调用(多态情况下):

子类中定义了与父类同名同参数的方法,在多态的情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译器是无法确定的。

例子:

Person e = new Student();

e.getInfo(); //调用Student类的getInfo()方法(),该方法是Student子类对其父类Person类中的重写

说明:编译时,e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类的getInfo()方法,这也叫动态绑定。

6.7 静态绑定与动态绑定

程序绑定的概念:绑定指的是一个方法的调用与方法所在的类(方法主体)关联起来。对于java,绑定可分为静态绑定与动态绑定。

静态绑定(早绑定):在程序执行前方法已经被绑定,即编译过程中,就知道了这个方法是哪个类的方法。

动态绑定(晚绑定):在运行时,根据具体对象的类型进行绑定。

注:如果不是动态绑定,那么它就不是多态。

6.8向上转型和向下转型:

父类 变量名1 = new 子类(); //向上转型(多态)

子类 变量名2 = (子类)变量名1; //向下转型

子类创建具体过程: 在一个子类被创建的时候,首先会在内存中创建一个父类对象,然后在父类对象外部放上子类独有的属性,两者合起来形成一个子类对象,所以子类可继承父类中所有的属性和方法。(在内存空间中,都会为其父类和子类创建并分配一个空间,其有一个地址值,其地址值前有名称@地址值,只有对用的名称才可以使用自己的属性和方法)

向上转型: 即用父类的引用变量去引用子类的实例,当向上转型之后,父类引用变量可以访问子类中属于父类的属性和方法,但是不能访问子类独有的属性和方法。

向下转型: 并不是所有对象都可以向下转型,只有当这个对象原本就是子类对象通过向上转型得到的时候才能够成功转型。

6.9 instanceof操作符

语法格式: x instanceof A

作用: 左边是对象,右边是类,检验x是否为类A或A的子类创建的实例对象,返回值为boolean型。

引出: 有了对象的多态性之后,内存中实际是加载了子类特有的属性和方法的(因为我们在new一个子类的时候,会调用其构造器,并且同样在构造器中会调用到其父类的构造器,因此在内存堆中,可定会加载子类和其父类的属性和方法),但由于变量声明为父类类型,导致编译时,只能调用父类中的属性和方法。子类特有的属性和方法不能调用。

解决如何调用子类特有的属性和方法:使用强制类型转换符,进行向下转型,将父类转换为子类,然后通过子类去调用其特有的属性和方法。

应用场景:为了防止在向下转型(进行强转时)出现转换类型异常(ClassCastException异常),我们要通过instanceof操作符在if语句中进行判断后,一旦返回true,再进行强转;一旦出现false,就不进行强制转型。

注:如果a instanceof A 返回值为true,则a instanceof B也返回true, 类B和类A的关系为继承关系。

7、Object类的使用

1.Object类是所有Java类的根父类

2.如果在类的声明中未使用extends关键字指明父类,则默认的父类为java.lang.Object类。

(可以通过 类名.getClass().getsuperclass()去查询某一个类的父类getclass()即获得当前该类)

3.Object类中的功能具有通用性。

属性:无

方法:equals()/ tostring() ……

3.1 clone():

在Object中的源码:

通过源码我们可以得出,该方法是通过protected修饰的,在调用时,需要扩大权限(即protected或public即可);并且该方法是一个本地方法(Native),效率高;其返回值是一个Object对象,因此我们进行克隆后还需要对返回值进行一个转型。

对于克隆,可以分为浅克隆和深克隆。

浅克隆(shallow clone):克隆是指拷贝对象时仅仅copy对象本身对象中的基本变量,而不拷贝对象包含的引用指向的对象。即只复制存在栈里的东西 (只复制基本类型的数据,引用类型的数据只复制了引用的地址,引用的对象并没有复制,在新的对象中修改引用类型的数据会影响原对象中的引用)

深克隆(deep clone):不仅克隆对象本身,而且克隆对象包含的引用指向的所有对象。

即递归向深层次进行克隆,举例:对象X中包含对Y的引用,Y中包含对Z的引用。浅拷贝X得到X1,X1中依然包含对Y的引用,Y中依然包含对Z的引用。深拷贝则是对浅拷贝的递归,深拷贝X得到X1,X1中包含对Y1(Y的copy)的引用,Y1中包含对Z1(Z的copy)的引用。

注:需要的克隆对象需要标记Cloneable接口,Cloneable是标记型的接口,它们内部都没有方法和属性,实现 Cloneable来表示该对象能被克隆,能使用Object.clone()方法。如果没有实现 Cloneable的类对象调用clone()就会抛出CloneNotSupportedException。

举例:对A类型进行克隆生成B,且A中有引用类型,需要进行深克隆。

类A:

类B:

测试类:

运行结果:

由于A类中有引用对象,如果仅仅用浅克隆的话,从p1克隆过来的p2对引用变量b的修改会相互影响,因为浅克隆的引用对象都是指向同一个地址的。那么这里我们需要用到深克隆,将p1对象中的引用类型b也进行进一步克隆,然后将克隆的结果转型后赋值给p2中的引用类型b,这样p2的b就会指向新克隆的地址,而不会再指向p1的引用对象b的地址,从而不会影响到p1的b。

3.2 equals(Object obj):比较两个对象是否相等

3.2.1 ==和equal的区别:

关于==运算符的使用:

1.可以使用在基本数据类型变量和引用数据类型变量中。

2.如果比较的是基本数据类型变量,比较两个变量保存的数据是否相等。(不一定要类型相同,可通过自动类型提升);如果比较的是引用数据类型变量,比较两者的地址值是否相等。

注:if(this.getOrderId() == o.getOrderId() && this.getOrderName() == o.getOrderName())return true;
因为string内容存放在常量池中,当有重复内容不同变量出现时,会复用,即指向同一个位置存放该具体内容所以会正确,但本质上还是错误的,不应该这么写。

equals()方法的使用

1.是一个方法,而非运算符。

2.只适用于引用数据类型。(只有对象才有方法)

3.Object类中equals的定义是:

public boolean equals(Object obj) {
	return (this == obj);
}

Object类中定义的equals()和==的作用是相同的。(在这里即表示比较两个引用数据变量的地址值是否相同)

4.像String、Date、File、包装类等都重写了object中的equal()的方法。重写以后比较的不是两个引用对象的地址是否相等,而是比较两个对象的实体内容是否相同。

3.2.2 重写equals()方法:

通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的实体内容是否相同,那么我们需要对object类中的equal()方法进行重写。

//手动实现equals()的重写
//比方说我们要比较某个类A的两个对象是否相同
//大致的格式如下
public class A {
	public String name;
	public int id;
	
	//对equals方法进行重写,否则调用该类A的equals方法时是比较的是两个地址值是否相同
	@Override
	public boolean equals(Object obj) {
		//如果两个比较对象指向同一块地址区,则两个肯定相同
		if(this == obj)return true;
		//如果比较的对象不是类型A,那么两者肯定不同
		if(obj instanceof A){
			//比较实体的内容是否相等
			A a = (A)obj;
			if(this.name.equals(a.name) && this.id == a.id)return true;
			else return false;
		}
		else return false;
	}
}

//自动实现equals的重写
//Eclipse点击Source下的Generate hashCode()和equals()
@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		A other = (A) obj;
		if (id != other.id)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}

3.3 finalize方法

垃圾回收机制中调用,在垃圾回收机制回收任何对象之前,总会先调用finalize方法,回收该不再使用的对象。永远不要主动调用某个对象的finalize方法,应该交给垃圾回收机制调用。

程序员可以通过System.gc()或者Runtime.getRuntime().gc()来通知系统进行垃圾回收,但是系统是否进行垃圾回收依然不确定。

3.4 getClass()方法

返回当前产生对象的类。

3.5 hashCode()

返回当前对象的哈希值。

3.6 toString()方法

3.6.1 知识点:

1.当我们输出一个对象的引用(即输出该对象名称)时,实际上就是调用当前对象的toString()方法。

2.toString()方法的定义:

//获取对象的类名+虚拟地址(通过Hashcode算出来的值,而不是真实的地址值)
public String toString() {
	return getClass().getName() + "@"Integer.toHexString(hashCode());
}

3.像String、Date、File、包装类都重写了Object类中的toString()方法。使得在调用对象的tostring()方法时,返回“实体内容”信息(具体关心的信息)。

4.自定义类也可以重写toString()方法,当调用此方法时,返回对象的“实体内容”

3.6.2 重写toString():

可以通过自己手写,也可以通过自动添加,和equals一样。

4.Object类只声明了一个空参构造器(即对所有的类,最终都会调用到该空参构造器)

8、包装类的使用

8.1 概念: 针对八种基本数据类型定义相应的引用类型,即是包装类(Wrapper,或封装类)

我们希望java中的基本数据类型,也具有类的一些特征和功能,因此我们给基本数据类型包装成包装类,这样java才是完全地面向对象。

8.2 基本数据类型、包装类、String三者之间的相互转换:

8.2.1 基本数据类型 --->包装类 (装箱)

//基本数据类型 --->包装类 (装箱)
	//调用包装类的构造器
	@Test
	public void test1(){
		//int类型的装箱:
		int num1 = 10;
		Integer int1 = new Integer(num1);
		System.out.println(int1.toString());
		Integer int2 = new Integer("123");
		System.out.println(int2.toString());
		
		//异常
//		Integer int3 = new Integer("123abc");
//		System.out.println(int2.toString());
		
		//浮点类型的装箱:
		Float f1 = new Float(12.3f);
		System.out.println(f1.toString());
		
		//boolean类型的装箱:
		//如何是true就是1,其他的都是0
		//boolean的默认值是false, Boolean类的默认值是null。
		Boolean b1 = new Boolean(true);
		Boolean b2 = new Boolean("true");
		Boolean b3 = new Boolean("true123");
		System.out.println(b1.toString());
		System.out.println(b2.toString());
		System.out.println(b3.toString());
	}

8.2.2 包装类--->基本数据类型(拆箱):

//包装类--->基本数据类型(拆箱)
	//调用包装类的.xxxvalue() 的方法
	@Test
	public void test2(){
		Integer int1 = new Integer(12);
		int i1 = int1.intValue();
		System.out.println(i1 + 5);
		
		//其他类型同理
		Float f1 = new Float(12.3f);
		float f2 = f1.floatValue();
		System.out.println(f2);
	}

8.2.3 jdk5.0新特性:自动装箱与自动拆箱:

//jdk5.0新特性:自动装箱与自动拆箱
	
	public void test3(){
		//自动装箱:基本数据类型 --->包装类 
		int num1 = 10;
		Integer int1 = num1;
		
		boolean b1 = true;
		Boolean b2 = b1;
		
		//自动拆箱:包装类--->基本数据类型
		int num2 = int1;
		boolean b3 = b2;
		
	}

8.2.4 基本数据类型、包装类--->string类型:

//基本数据类型、包装类--->string类型:
	@Test
	public void test4(){
		//方式1:通过连接符“+”,进行连接运算
		int num1 = 10;
		String str1 = num1 + "";
		//方法2:调用String重载的valueof([类型]Xxx xxx)
		float f1 = 12.3f;
		String str2 = String.valueOf(f1);
		
		//包装类使用的是Object object的形参
		Double d1 = new Double(12.4);
		String str3 = String.valueOf(d1);
	}

8.2.5 string类型--->基本数据类型、包装类:

//string类型--->基本数据类型、包装类:
	//方法:调用包装类的parseXxx();
	@Test
	public void test5(){
		String str1 = "123";
		//先转换成int包装类,然后通过自动拆箱,赋值给int类型
		int num1 = Integer.parseInt(str1);
		System.out.println(num1);
		
	}

转换图:

注:integer内部定义了IntegerCache结构,IntegerCache中定义了Integer[],保存了从-128~127范围的整数。如果我们使用自动装箱的方式,给Integer赋值的范围在该范围内的时候,可以直接使用该数组中的元素,不用再去new一个,并且在不使用时,也不会被消耗,主要目的是提高效率。(即地址相同)

9、单元测试方法的使用

步骤:

  1. 选中当前工程 - 右键选择: build path - add libraries - JUnit 4 - 下一步
  2. 创建Java类,进行单元测试。此时的Java类的要求:a、此类是public b、此类提供公共的无参的构造器。
  3. 此类中声明单元测试方法,此时的单元测试方法要求:方法的权限是public,没有返回值,没有形参。
  4. 此单元测试方法上需要声明注解:@Test,并在单元测试类中导入:import org.junit.Test;
  5. 声明好单元测试方法以后,就可以子方法体内测试相关代码。
  6. 写完代码以后,左键双击测试方法名,右击:run as - JUnit Test进行测试

说明:

1.如果执行结果没有任何异常:绿条

2.如果执行结果出现异常:红条

标签:重写,调用,对象,子类,面向对象编程,父类,方法
From: https://www.cnblogs.com/bananayjy/p/17071772.html

相关文章

  • 06-JavaSE:面向对象编程
    面向过程的思维模式面向过程的思维模式是简单的线性思维,思考问题首先陷入第一步做什么、第二步做什么的细节中。这种思维模式适合处理简单的事情,比如:上厕所。如果面对......
  • 【学懂Java】(四)面向对象编程-5
    一.Object类Object类是所有类的父类,一个类如果没有使用extends显性的继承另外一个类,那么这个类就继承自Object类publicclassPerson{}//等同于publicclassPersonextends......
  • 【学懂Java】(四)面向对象编程-3
    一.代码块1.概念使用”{}”括起来的一段代码2.分类根据位置可分类普通代码块:定义在方法中的使用{}括起来的代码publicclassCodeBlockDemo{publicvoidtest(){......
  • 【学懂Java】(四)面向对象编程-4
    一.继承(面向对象编程四大特征)继承优化前:代码重复,不利于修改继承优化后:代码简明,利于修改1.概念类是对对象的抽象,继承是对某一批类的抽象,从而实现对现实世界更好的建模。提高......
  • 【学懂Java】(四)面向对象编程-2
    一.局部变量和成员变量局部变量成员变量(全局变量)定义在方法中定义在方法外,类之内的变量栈内存中堆内存中局部变量没有默认值成员变量有默认值当前方法当前类的方法不同的方......
  • 【学懂Java】(四)面向对象编程-1
    一.OOP:ObjectOrientedProgramming1.面向过程和面向对象面向过程面向对象区别事物比较简单,可以用线性的思维去解决事物比较复杂,使用简单的线性思维无法解决共同点1.面向......
  • 面向过程编程和面向对象编程的区别
    面向过程编程和面向对象编程的区别此篇文章来自一个初学Java不久的学生,内容的用词、深度、广度甚至部分理解不够到位,再加上Markdown语法的不熟练,所以排版不够美观。但还......
  • 面向对象编程(OOP)
    面向对象编程(OOP)属性+方法=类面向过程步骤清晰简单,第一步做什么,第二步做什么...适用于处理简单的问题面向对象物以类聚和分类的思想模式思考解决问题需要......
  • Python学习六:面向对象编程(上)
    文章目录​​前言​​​​一、面向对象编程:​​​​1.oop[objectorientedprogramming]是一种python的编程思路​​​​2.解释​​​​3.面向对象和面向对象编程​​......
  • Python学习八:面向对象编程(下):异常、私有等
    文章目录​​前言​​​​一、私有化属性​​​​1.引入私有化属性​​​​2.语法(定义)​​​​3.私有属性的特性​​​​4.使用的场景​​​​5.最后小结​​​​二、......