JavaSE---04面向对象(中)

04 面向对象(中)


4.1 继承extends

  • 为什么要有继承?
    多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,
    那么多个类无需再定义这些属性和行为,只要继承那个类即可。

  • 继承性的好处
    ① 减少了代码的冗余,提高了代码的复用性;
    ② 便于功能的扩展;
    ③ 为之后多态性的使用,提供了前提。

  • 继承性的格式
    class A extends B{}
    A:子类、派生类、subclass
    B:父类、超类、基类、superclass

    1 体现:一旦子类 A 继承父类以后,子类 A 中就获取了父类 B 中声明的结构:属性、方法
    特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构
    只有因为封装性的影响,使得子类不能直接调用父类的结构而已。(见下图)
    **2 **子类继承父类以后,还可以声明自己特有的属性或方法,实现功能的拓展。
    子类和父类的关系:不同于子集与集合的关系。
    extends:延展、扩展

img
  • Java 中关于继承性的==规定==:

​ 1.一个类可以被多个类继承

​ 2.Java 中类的继承性:一个类只能有一个父类

​ 3.子父类是相对的概念。

​ 4.子类直接继承的父类,称为:直接父类。间接继承的父类,称为,间接父类。

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

  • 注意

​ 1.如果我们没有显式的声明一个类的父类的话,则此类继承于 java.lang.Object 类

​ 2.所有的 java 类(除 java.long.Object 类之外)都直接或间接地继承于 java.lang.Object 类;

​ 3.意味着,所有的 java 类具有 java.lang.Object 类声明的功能。

4.2 方法的重写overwrite

概念

子类继承父类以后,可以对父类中的方法进行覆盖操作。

应用

重写以后,当创建子类对象以后,通过子类对象去调用子父类中同名同参数方法时,执行的是子类重写父类的方法。即在程序执行时,子类的方法将覆盖父类的方法。

面试题–区分方法的重载与重写

方法的重写Overriding和重载Overloading是Java多态性的不同表现。

重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。

如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。

子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被"屏蔽"了。

如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。

规定

子类重写的方法的方法名和形参列表必须和父类被重写的方法的方法名、形参列表相同

子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限,

特殊情况: 子类不能重写父类中声明为private权限的方法;

返回值类型:

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

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

​ 父类被重写的方法的返回值类型如果是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须是:double)。

子类方法抛出的异常不能大于父类被重写的方法抛出的异常;

子父类同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static(不是重写)

代码示例

public class OverwriteTest {
    public static void main(String[] args) {
        Student s = new Student();
        s.show();
        s.study();//两种结果

    }
}

class Person{
    public void study(){
        System.out.println("好好学习");
        show();
    }

    //如果是private则不是重写,所以上面study中调用的是父类的show;若改为public则是子类的show
    private void show(){
        System.out.println("我是一个人");
    }

    public void eat(){
        System.out.println("吃他丫的");
    }
}

class Student extends Person{
    public void show(){
        System.out.println("我是一个学生");
    }
}

4.3 关键字super

概念

1.super理解为:父类的

2.super可以用来调用:属性、方法、构造器

使用

  • 我们可以在子类的方法或构造器中,通过"super.属性"或"super.方法"的方式,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯去省略这个"super."

  • 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的使用"super.属性"的方式,表明调用的是父类中声明的属性

  • 特殊情况:当子类重写了父类中的方法后,我们想在子类的方法中调用父类中被重写的方法时,必须显式的使用"super.方法"的方式,表明调用的是父类中被重写的方法

super调用构造器

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

  • "super(形参列表)"的使用,必须声明在子类构造器的首行

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

  • 构造器的首行,既没有显式的声明"this(形参列表)“或"super(形参列表)”,则默认的调用的是父类中的空参构造器。super()所以我们平常写构造器时通常写一个空的构造器

  • 在类的多个构造器中,至少有一个类的构造器使用了"super(形参列表)",调用父类中的构造器。

代码

Student:
public Student(String name,int age,String major){
// 		super();//这句默认就有
//		this.age = age;
//		this.name = name;
		super(name,age);//若没这句,默认掉用super(),就会打印“我无处不在!”
		this.major = major;
	}

Person类:
public Person(){
    System.out.println("我无处不在");
}    
public Person(String name){
    this.name = name;
}
public Person(String name,int age){
    this(name);
    this.age = age;
}

4.4 子类对象实例化的过程

结果上看

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

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

过程上看

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

图解

img

4.5 多态性

4.5.1 多态性概念及示例

概念

  • 理解多态性:可以理解为一个事物的多种态性。
  • 何为多态性:对象的多态性,即父类的引用指向子类的对象(或子类的对象赋值给父类的引用)
  • 多态的使用:虚拟方法调用
    • 有了对象多态性以后,我们在编译期,只能调用父类声明的方法,但在执行期实际执行的是子类重写父类的方法。简称:编译时,看左边;运行时,看右边。若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)
    • 多态情况下,
      “看左边”:看的是父类的引用(父类中不具备子类特有的方法)
      “看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)
  • 多态性的使用前提
    ① 类的继承关系
    ② 方法的重写
  • 对象的多态性:只适用于方法,不适用于属性(编译和运行都看左边)

示例

public class AnimalTest {
	public static void main(String[] args) {
		AnimalTest test = new AnimalTest();
		test.func(new Dog());//调的就是dog的
		test.func(new Cat());
	}

	public void func(Animal animal){	//Animal animal = new Dog();
		animal.eat();
		animal.shout();
	}
	
	//如果没有多态性,就会写很多如下的方法,去调用
	public void func(Dog dog){
		dog.eat();
		dog.shout();
	}
	public void func(Cat cat){
		cat.eat();
		cat.shout();
	}
}

class Animal{
	public void eat(){
		System.out.println("动物,进食");
	}
	
	public void shout(){
		System.out.println("动物:叫");
	}
}

class Dog extends Animal{
	public void eat(){
		System.out.println("狗吃骨头");
	}
	
	public void shout() {
		System.out.println("汪!汪!汪!");
	}
}

class Cat extends Animal{
	public void eat(){
		System.out.println("猫吃鱼");
	}
	
	public void shout() {
		System.out.println("喵!喵!喵!");
	}
}

4.5.2 虚拟方法调用(父类的方法)

/* 从编译和运行的角度看:
 * 重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称  做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法
 *
 * 对于重载而言,在方法调用前,编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定”;
 * 而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”。
 * 引用一句Bruce Eckel的话:“不要犯傻,如果它不是晚绑定,它就不是多态。”
 */
//面试题:多态是编译时行为还是运行时行为?
//证明如下:
class Animal  {
	protected void eat() {
		System.out.println("animal eat food");
	}
}

class Cat  extends Animal  {
	protected void eat() {
		System.out.println("cat eat fish");
	}
}

class Dog  extends Animal  {
	public void eat() {
		System.out.println("Dog eat bone");
	}
}

class Sheep  extends Animal  {
	public void eat() {
		System.out.println("Sheep eat grass");
	} 
}

public class InterviewTest {

	public static Animal  getInstance(int key) {
		switch (key) {
		case 0:
			return new Cat ();
		case 1:
			return new Dog ();
		default:
			return new Sheep ();
		}
	}

	public static void main(String[] args) {
		int key = new Random().nextInt(3);

		System.out.println(key);
		Animal  animal = getInstance(key);
		animal.eat();	 
	}
}

4.6 向下转型与关键字instanceof

向下转型的背景

​ 有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类的属性和方法不能调用

​ 如何才能调用子类所特有的属性和方法?使用强制类型转换符,也可称为:向下转型。

Person p2 = new Man();
//p2.isSmoking = true;//报错,因为man对象的特有属性Person看不到
Man m1 = (Man) p2;
m1.earnMoney();
m1.isSmoking = true;
// 使用强转时,可能出现ClassCastException异常
// Woman w1 = (Woman)p2;//因为这时p2已经被强转为man
// w1.goShopping();

instanceof

a instanceof A:判断对象a是否是类A的实例。如果是,返回true,如果不是,返回false

  • 使用情境:为了避免在向下转型时出现ClassCastException异常,我们在进行向下转型之前,先进行 instanceof的判断,一旦返回true,就进行向下转型。如果返回false,不进行向下转型。
  • 如果a instanceof A返回true,则a instanceof B也返回true。 其中类B是类A的父类。
if (p2 instanceof Woman) {
    Woman w1 = (Woman) p2;
    w1.goShopping();
    System.out.println("**********Woman*********");
}

if (p2 instanceof Man) {
    Man m2 = (Man) p2;
    m2.earnMoney();
    System.out.println("*********Man************");
}

4.7 多态的练习

练习一

/* 练习:子类继承父类
 * 1.若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,
 * 系统将不可能把父类里的方法转移到子类中。
 * 
 * 2.对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,
 * 这个实例变量依然不可能覆盖父类中定义的实例变量
 */
public class FieldMethodTest {
	public static void main(String[] args){
		Sub s= new Sub();
		System.out.println(s.count);	//20
		s.display();//20
		
		Base b = s;
	//==:对于引用数据类型来讲,比较的是两个引用数据类型变量的地址值是否一样。
		System.out.println(b == s);	//true
		System.out.println(b.count);	//10
		b.display();//20
	}
}

class Base {
	int count= 10;
	public void display() {
		System.out.println(this.count);
	}
}

class Sub extends Base {
	int count= 20;
	public void display() {
		System.out.println(this.count);
	}
}

面试题拓展

/* 考查多态的笔试题目:
 * 面试题:多态是编译时行为还是运行时行为?如何证明?
 * 
 * 拓展问题
 */
public class InterviewTest1 {

	public static void main(String[] args) {
		Base base = new Sub();
		base.add(1, 2, 3);//sub_1 可变个数形参与同类型数组视为一样,所以
        				  //构成重写

//		Sub s = (Sub)base;
//		s.add(1,2,3); //sub_2 参数个数已知的优先级高于未知的
	}
}

class Base {
	public void add(int a, int ... arr) {
		System.out.println("base");
	}
}

class Sub extends Base {

	public void add(int a, int[] arr) {
		System.out.println("sub_1");
	}

//	public void add(int a, int b, int c) {
//		System.out.println("sub_2");
//	}

}

4.8 Object类

概念

java.lang.Object1.Object类是所有Java类的根父类;
2.如果在类的声明中未使用extends关键字指明其父类,则默认父类java.lang.Object3.Object类中的功能(属性、方法)就具有通用性。
属性:无
方法:equals() / toString() / getClass() / hashCode() / clone() /finalize() / wait()notify()notifyAll()
4.Object类只声明了一个空参的构造器。

常用函数

img

所有方法

img

4.8.1 ==操作符与equals方法

二者区别

一、回顾==的使用
 == : 运算符
 1.可以使用在基本数据类型变量和引用数据类型变量中
 2.如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型要相同)
   如果比较的是引用数据类型变量:比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体
 补充: == 符号使用时,必须保证符号左右两边的变量类型一致。

二、equals()方法的使用
1.是一个方法,而非运算符
2.只能适用于引用数据类型。
3.Object类中equals()的定义:
		public boolean equals(Object obj){
 			return (this == obj);
 		}
 说明:Object类中定义的equals()==的作用是相同的,比较两个对象的地址值是否相同,即两个引用是否指向  同一个对象实体。
4.StringDateFile、包装类等都重写了Object类中的equals()方法.
  两个引用的地址是否相同,而是比较两个对象的“实体内容”是否相同。
5.通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的"实体内容"是否相同。那么,我们就需要对Object类中的equals()进行重写。

重写的原则:比较两个对象的实体内容是否相同。

代码示例

//基本数据类型
int i = 10;
int j = 10;
double d = 10.0;
System.out.println(i == j);	//true 会自动提升
System.out.println(i == d); //true

char c = 10;
System.out.println(i == c); //true 因为char本来就是存的ASICC码

char c1 = 'A';
char c2 = 65;
System.out.println(c1 == c2); //true

//引用数据类型
Customer cust1 = new Customer("Tom" ,21);
Customer cust2 = new Customer("Tom" ,21);
System.out.println(cust1 == cust2); //false 这需要进一步重写equals

String str1 = new String("atguigu");
String str2 = new String("atguigu");
System.out.println(str1 == str2); //false 比较的地址
System.out.println("*************************");
System.out.println(cust1.equals(cust2));	//false
System.out.println(str1.equals(str2));	//true

Date date1 = new Date(23432525324L);
Date date2 = new Date(23432525324L);
System.out.println(date1.equals(date2));//true

//重写Customer中的equals()
public boolean equals(Object obj) {		
    if(this == obj){
        return true;
    }
    if(obj instanceof Customer){
        Customer cust = (Customer)obj;
        //比较两个对象的属性是否都相同
        if(this.age == cust.age && this.name.equals(cust.name)){
            return true;
        }else{
            return false;
        }
        //或
        //return this.age == cust.age && this.name.equals(cust.name);
    }
    return false;
}

4.8.2 toString()方法

概念

  1. 当我们输出一个引用对象时,实际上就是调用当前对象的toString()

  2. Object类中toString的定义方法:

    public String toString() {

    ​ return getClass().getName() + “@” + Integer.toHexString(hashCode());

    }

  3. 像String、Date、File、包装类等都重写了Object类中的toString()方法。使得在调用toString()时,返回"实体内容"信息

  4. 自定义类如果重写toString()方法,当调用此方法时,返回对象的"实体内容"

代码

Customer cust1 = new Customer("Tom" ,21);
System.out.println(cust1.toString());//github4.Customer@15db9742
System.out.println(cust1); //github4.Customer@15db9742	
String str = new String("MM");
System.out.println(str);//MM  因为已经被重写了

4.9 单元测试方法 test

IDEA中需要当前项目右键-》project instructure-》modules-》添加jar包,从IDEA的lib下找到junit4-13和hamcrest-core-1.3.jar

/*
 * java中的JUnit单元测试
 * 步骤:
 * 1.选中当前项目工程 --》 右键:build path --》 add libraries --》 JUnit 4 --》 下一步
 * 2.创建一个Java类进行单元测试。
 * 	 此时的Java类要求:①此类是公共的 ②此类提供一个公共的无参构造器 
 * 3.此类中声明单元测试方法。
 *   此时的单元测试方法:方法的权限是public,没有返回值,没有形参。
 * 4.此单元测试方法上需要声明注解:@Test并在单元测试类中调用:import org.junit.Test;
 * 5.声明好单元测试方法以后,就可以在方法体内测试代码。
 * 6.写好代码后,左键双击单元测试方法名:右键 --》 run as --》 JUnit Test
 * 
 * 说明:如果执行结果无错误,则显示是一个绿色进度条,反之,错误即为红色进度条。
 */

@Test
public void testEquals(){
    String s1 = "MM";
    String s2 = "MM";
    System.out.println(s1.equals(s2));

    //ClassCastException的异常
    //Object obj = new String("GG");
    //Date date = (Date)obj;

    System.out.println(num);
    show();
}

4.10 包装类

4.10.1 概念及示例

包装类

基本数据类型		包装类
byte			Byte
short			Short
int 			Integer
long			Long
float			Float
double			Double
boolean			Boolean
char			Character
注意:其中ByteShortIntegerLongFloatDouble的父类是:Number

包装类、基本数据类型、String相互转换

img

自动装箱与自动装箱

//自动装箱:基本数据类型 --》 包装类
int num2 = 10;
Integer in1 = num2;//自动装箱

boolean b1 = true;
Boolean b2 = b1;//自动装箱

//自动拆箱:包装类 --》 基本数据类型
System.out.println(in1.toString());
int num3 = in1; 

//基本数据类型、包装类---》String类型,调用String重载的valueOf(Xxx xxx)
int num1 = 10;
//方式1:连接运算
String str1 = num1 + "";
//方式2:调用String的valueOf(Xxx xxx)
float f1 = 12.3f;
String str2 = String.valueOf(f1); //"12.3"

Double d1 = new Double(12.4);
String str3 = String.valueOf(d1);
System.out.println(str2);
System.out.println(str3);	//"12.4"

//String类型---> 基本数据类型、包装类,调用包装类的parseXxx(String s)
String str1 = "123";
//String str1 = "123a";

int num2 = Integer.parseInt(str1); 
System.out.println(num2 + 1);	//124

String str2 = "true";//若是true1,则结果为false,因为Boolean判断只要不是true都输出false
Boolean b1 = Boolean.parseBoolean(str2);
System.out.println(b1);	//true

4.10.2 题目

面试题

/*
 * 如下两个题目输出结果相同吗?各是什么:
 * 		Object o1= true? new Integer(1) : new Double(2.0);
 * 		System.out.println(o1);//
 * 
 * 		Object o2;
 * 		if(true)
 * 			o2 = new Integer(1);
 *		else 
 *			o2 = new Double(2.0);
 *		System.out.println(o2);//
 */
public class InterViewTest {

	@Test
	public void test(){
		Object o1= true? new Integer(1) : new Double(2.0);//在编译时就将二者变为同样的类
		System.out.println(o1);// 1.0                       型,自动类型提升
	}
	
	@Test
	public void test2(){
		Object o2;
		if(true)
			o2 = new Integer(1);
		else 
			o2 = new Double(2.0);
		System.out.println(o2);// 1
	}
	
	@Test
	public void method1() {
		Integer i = new Integer(1);
		Integer j = new Integer(1);
		System.out.println(i == j); //false
		
	    //Integer内部定义了一个IntegerCache结构,IntegerCache中定义Integer[]
		//保存了从-128-127范围的整数。如果我们使用自动装箱的方式,给Integer赋值的范围在其中时,
		//可以直接使用数组中的元素,不用再去new了。目的,提高效率。
		
		Integer m = 1;
		Integer n = 1;
		System.out.println(m == n);//true
		
		Integer x = 128;//相当于new了一个Integer对象
		Integer y = 128;//相当于new了一个Integer对象
		System.out.println(x == y);//false
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值