【Java】面向对象笔记(中)(二)-创新互联

继承再续 super关键字 理解

super理解为:父类的

站在用户的角度思考问题,与客户深入沟通,找到西峡网站设计与西峡网站推广的解决方案,凭借多年的经验,让设计与互联网技术结合,创造个性化、用户体验好的作品,建站类型包括:做网站、成都网站建设、企业官网、英文网站、手机端网站、网站推广、国际域名空间、网站空间、企业邮箱。业务覆盖西峡地区。

super可以用来调用:属性super.attr、方法super.method()、构造器super()

应用 注意点
  • 我们可以在子类的方法或构造器中。通过使用super.attrsuper.method()的方式,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略super.
  • 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的使用super.attr的方式,表明调用的是父类中声明的属性。
  • 特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的使用super.method()的方式,表明调用的是父类中被重写的方法。
父类构造器
  • 我们可以在子类的构造器中显式的使用"super(形参列表)"的方式,调用父类中声明的指定的构造器
    super(形参列表)的使用,必须声明在子类构造器的首行!
  • 我们在类的构造器中,针对于this(形参列表)super(形参列表)只能二选一,不能同时出现⭐
  • 在构造器的首行,没有显式的声明this(形参列表)super(形参列表),则默认调用的是父类中空参的构造器:super()
  • 在类的多个构造器中,至少有一个类的构造器中使用了super(形参列表),调用父类中的构造器
子类对象实例化的全过程

从结果上来看:(继承性)

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

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

从过程上来看:

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

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

多态性 Polymorphism

多态性较难理解,请仔细思考辨析。

哲学三问

这三个问题,请在第一遍时只看序号1的内容,第二遍再看序号2的内容

什么是多态
  1. 父类的引用指向子类的对象(或子类的对象赋给父类的引用)

    可以理解为一个事物的多种形态

    引用Charlie Calverts的对多态的描述

    多态性是允许你将父对象设置成为和一个或更多的“相当于他的子对象的对象”的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作(摘自《Delphi4 编程技术内幕》)

  2. 所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。

    Java引用变量有两个类型: 编译时类型和运行时类型。 编译时类型由声明该变量时使用的类型决定, 运行时类型由实际赋给该变量的对象决定。 简称: 编译时, 看左边;运行时, 看右边。

为什么要有多态
  1. 比如你是一个酒神,对酒情有独钟。某日回家发现桌上有几个杯子里面都装了白酒,从外面看我们是不可能知道这是些什么酒,只有喝了之后才能够猜出来是何种酒。你一喝,这是剑南春、再喝这是五粮液、再喝这是酒鬼酒….在这里我们可以描述成如下:

    酒 a = 剑南春

    酒 b = 五粮液

    酒 c = 酒鬼酒

    这里所表现的的就是多态。剑南春、五粮液、酒鬼酒都是酒的子类,我们只是通过酒这一个父类就能够引用不同的子类,这就是多态——我们只有在运行的时候才会知道引用变量所指向的具体实例对象。

  2. 因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。

怎么使用多态
  1. 虚拟方法调用–比如我现在定义了两个类PersonManMan继承自Person并重写了父类中的eat()walk()方法
//对象的多态性:将子类对象赋给父类变量
Person p2 = new Man();
//多态的使用
p2.eat();
p2.walk();

当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法。这被称为虚拟方法调用。你想想,程序在编译时一位p2是Person对象,但在执行过程中才发现p2是子类对象,进而调用子类重写的方法。利用这一特性,我们可以在某些无法确定具体对象类型的场合,先使用父类对象代替,程序在执行时才决定具体是哪个子类对象类型。

  1. 开发环境中我们经常会这样用:比如我有一个Animal类,并中定义shout()方法,再定义子类CatDog两个类,分别重写shout()方法。

    class Animal{public void shout(){System.out.println("动物:叫");
    	}	
    }
    
    class Dog extends Animal{public void shout(){System.out.println("汪!汪!汪!");
    	}
    
    }
    class Cat extends Animal{public void shout(){System.out.println("喵!喵!喵!");
    	}
    }

    再在测试类中定义一个方法func,接收父类作为参数,我希望在接收到Dog对象时,会根据dog重写的方法来反应

    public static void main(String[] args) {AnimalTest test = new AnimalTest();
    		test.func(new Dog());//汪!汪!汪!
    		test.func(new Cat());//喵!喵!喵!
    }
    	
    public void func(Animal animal){//Animal animal = new Dog();
        animal.shout();
    }

    经过测试,当向方法中传入dog对象,结果是汪!汪!汪!

必要条件

Java实现多态有三个必要条件:继承、重写、向上转型。

继承:在多态中必须存在有继承关系的子类和父类。

重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。

向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。

使用注意

对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)

再次区分重载与重写

之前我们是从定义方面描述

重载,是一种语言特性,是同一方法在面对不同参数时有不同的方法对应。记住“两同一不同”:相同类,相同方法名,不同形参。如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。

重写,多态性的一种表现,是子类对父类方法相同方法的覆盖,子类重写的是父类中同名同参数的方法。也就是相同参数时,子类使用不同的方法来对应。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被"屏蔽"了。

现在从编译运行的角度来看:

重载,是指允许存在多个同名方法,而这些方法的参数不同。 编译器根据方法不同的参数表, 对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。 它们的调用地址在编译期就绑定了。 Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。

所以: 对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定”

而对于多态,只有等到方法调用的那一刻, 解释运行器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定” 。

引用一句Bruce Eckel的话: “不要犯傻,如果它不是晚绑定, 它就不是多态。”

其中,易错点是:重载只是一种语言特性,与多态无关,与面向对象也无关!

向下转型 强制类型转换符

在基本数据类型中,我们使用强制类型转换符,可以将double类型强行转换为int类型,代价是损失精度。

在类中我们也可以强制从父类转换成子类,这样就可以使用子类的特有方法,代价就是,有可能转换失败。比如你本来是new Man(),但在强转时却转成了Woman类型,此时编译可以通过,但运行不成功。会出现ClassCastException的异常。

Java转型对比

//强行向下转型
Man m1 = (Man)p2;
m1.earnMoney();
instanceof关键字

为了避免转型失败造成程序中止,我们可以先判断

if (p2 instanceof Woman) {//new Man(),所以结果应为false
    Woman m1 = (Woman)p2;
    m1.goShopping();
}

并且,如果判断的是父类,也会返回true

System.out.println(p2 instanceof Object);//true
几点注意
  1. 这里所说的向下转型,一定是先由子类构造器生成了子类对象,但由于多态性被转成父类对象。此时的父类对象才能够被转型,因为其拥有子类对象的所有特性。但若直接是父类构造器生成的,它便没有子类的特性,也就无法转型。

    这一点要与基本类型的转换做区分。

  2. 若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中:编译看左边,运行看右边

  3. 对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量:编译运行都看左边

Object类介绍

java.lang.Object类

所有Java类的根父类

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

Object类中的功能(属性、方法)具有通用性。

  • 属性:无

  • 方法:equals() 、toString()、getClass()、hashCode()、clone()、finalize()、wait() 、 notify()、notifyAll()

Object类只声明了一个空参的构造器

运算符的回顾

面试题: == 和 equals() 区别

回顾 == 的使用:

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

  1. 如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型要相同)

  2. 如果比较的是引用数据类型变量:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体

    补充: == 符号使用时,必须保证符号左右两边的变量类型一致。

equals()方法的使用:

只能适用于引用数据类型

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

  2. Object类中equals()的定义:

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

    说明:Object类中定义的equals()==的作用是相同的:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体

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

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

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

实际开发中,我们通常使用IDE自动生成equals()方法

如果自己重写equals()方法,可以这样:

@Override
    public boolean equals(Object obj) {if (obj == this) {return true;
        }
        if (obj instanceof MyDate) {MyDate myDate = (MyDate)obj;//强转是为了调用子类的属性。
            return this.year.equals(myDate.year) && this.month.equals(myDate.month) && this.day.equals(myDate.day);
        }//在这里如果比较基本数据类型,用==;比较引用数据类型用对象自己的equals,另外,你自己定义的类记得重写
        return false;
    }
toString()

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

Object类中toString()的定义:

public String toString() {return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

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

自定义类也可以重写toString()方法,当调用此方法时,返回对象的"实体内容",这个技术含量不高,用IDE自动生成吧。

单元测试

使用Junit进行单元测试

怎么使用
  1. 给当前项目的libraries中添加junit

  2. 创建Java类,进行单元测试。

    此时的Java类要求:

    • 此类是public的
    • 此类提供公共的无参的构造器
  3. 此类中声明单元测试方法。

    方法的权限是public,没有返回值,没有形参

    @Test

    并在单元测试类中导入:import org.junit.Test;

  4. 此单元测试方法上需要声明注解

说明

如果方法没有异常

正常界面

如果方法有异常

异常界面包装类(Wrapper) 哲学三问 什么是包装类

包装类,顾名思义就是将什么经过包装的类,那么是将什么包装起来的呢?显然这里是将基本类型包装起来的类。包装类的作用就是将基本类型转成对象,将基本类型作为对象来处理。

为什么需要包装类

Java 语言是一个面向对象的编程语言,但是 Java 中的基本数据类型却不是面向对象的,但是我们在实际使用中经常需要将基本数据类型转换成对象,便于操作。

比如,集合的操作中,我们就需要将基本类型数据转化成对象,所以就出现了包装类。

怎么使用包装类

Integer number = new Integer(123);

包装类的种类包装类

此图中标红的名称并不是将首字母大写的来的,方框内的类都有一个父类Number

继承关系可看下图

包装类的继承关系拆装箱详解

在了解自动拆装箱之前,我们得先知道什么是拆箱和装箱。其实拆装箱主要应对基本类型与包装类型的相互转换问题。

  • 装箱:将基本类型转换成包装类型的过程叫做装箱。
  • 拆箱:将包装类型转换成基本类型的过程叫做拆箱。

拆装箱详解

其实,在JDK1.5版本之前,是没有自动拆装箱的,开发人员要手动进行装拆箱:

//手动装箱,也就是将基本类型10转换为引用类型
Integer integer = new Integer(10);
//或者
Integer integer1 = Integer.valueOf(10);

//手动拆箱,也就是将引用类型转换为基本类型
int num = integer.intValue();//num==10

而在在 JDK1.5 版本之后,为了减少开发人员的工作,提供了自动装箱与自动拆箱的功能。如下方代码所示:

只需要像基本数据类型一样用就行了

//自动装箱
Integer one = 1;
//自动拆箱
int two = one + 10;
基本数据类型、包装类与String类之间转化 由基本数据类型、包装类向String类
Integer i2 = 12;
//方式一:直接拼接
String s2 = i2 + "";
//方式二:用valueOf()
String s2 = i2 + "";
由String类向基本数据类型、包装类

通过parseInt()等方法

String s = "123";
int i1 = Integer.parseInt(s);

需要注意的是,如果数据不干净,会报错:NumberFormatException

String s = "trUe";
int i1 = Integer.parseInt(s);//true

只有Boolean类特殊,不会报错

Parses the string argument as a boolean. Thebooleanreturned represents the valuetrueif the string argument is notnulland is equal, ignoring case, to the string"true".

把字符串参数解析为一个布尔值。如果字符串不为空并且等于true(忽略大小写)布尔值返回true。

常见面试题
public class InterviewTest {@Test
	public void test1() {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 test3() {Integer i = new Integer(1);
		Integer j = new Integer(1);
		System.out.println(i == j);//false
		
		//Integer内部定义了IntegerCache结构,IntegerCache中定义了Integer[],
		//保存了从-128~127范围的整数。如果我们使用自动装箱的方式,给Integer赋值的范围在
		//-128~127范围内时,可以直接使用数组中的元素,不用再去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
	}
}

对于test3的调试图

test3

我们可以看到i3,i4的地址不同。

或许你还有疑问,我们之前不是在输出时使用表达式计算吗?System.out.println(i1 + i2);这个时候它怎么自动拆箱然后算出了值。

问题在于i1 == i2这是一个布尔值表达式。这会使得println()调用

voidjava.io.PrintStream.println(booleanx)

Prints a boolean and then terminate the line. This method behaves as though it invokesprint(boolean)and thenprintln().

然而i1 + i2是一个int值的表达式。他会调用

voidjava.io.PrintStream.println(intx)

Prints an integer and then terminate the line. This method behaves as though it invokesprint(int)and thenprintln().

在print时会自动调用valueOf方法

public void print(int i) {write(String.valueOf(i));
}

所以会自动拆箱取值。


本文参考:

java提高篇(四)-----理解java的三大特性之多态

浅谈多态——概念描述

Java工具类——包装类

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


本文名称:【Java】面向对象笔记(中)(二)-创新互联
本文URL:http://hbruida.cn/article/dpjpeh.html