1.面向对象特征之二———继承性
1.1继承性的好处
<1>减少代码冗余,提高了代码的复用性;
<2>便于功能的扩展;
<3>为多态性的使用提供前提。
1.2继承的格式
public/缺省 class A extends class B {}
A:称作子类,派生类,subclass
B:称作父类,超类,基类,superclass
1.3子类继承父类后有哪些不同
体现:一旦子类A继承父类B后,子类A中就获取了父类B中的声明的所有结构:属性,方法。特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构,但因为封装性的影响,使得子类不能直接调用父类的结构而已。
且子类继承父类后,还可声明自己特有的属性或方法,实现功能的拓展。
1.4Java中关于继承的规定
<1>一个子类只能有一个父类(单继承性);
<2>一个父类可以派生出多个子类;
<3>字父类是相对的概念;
<4>直接父类/间接父类,若A是B的父类,B是C的父类,那么A和B都是C的父类,且A是C的间接父类,B是C的直接父类;
<5>子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法;
<6>若没有显示地声明一个类的父类的话,则子类继承于java.lang.Object,所有的Java类(除了Object类外),都直接或间接地继承java.lang.Object类。
2.方法的重写(override/overwrite)
2.1方法重写的定义
子类继承父类后,可以对父类中同名同参的方法,进行覆盖操作。
2.2方法重写的要求(规定)
首先:子类中的叫做重写的方法,父类中的叫被重写的方法
<1>子类的重写的方法的方法名和形参列表应与父类中被重写的方法保持一致;
<2>子类的重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符(注意,子类中无法重写父类中权限为private的方法,子类中能写出来,但不是重写);
<3>返回值类型:
1)父类中被重写的方法的返回值类型为void,则子类中重写的方法也必须为void;
2)父类中被重写的方法的返回值类型为A,则子类中重写的方法为A或A的子类;
3)父类在被重写的方法的返回值类型为基本数据类型,则子类中重写的方法必须是相同的基本数据类型;
4)子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型。
<4>子类和父类中同名同参的方法要么都声明为非static的(重写仅限于对非static的方法而言),要么都声明为static的(这其实不是重写了,只是这样写语法上没有问题)
3.关键字:super
3.1简单介绍
<1>super理解为:父类的......
<2>super可以调用父类中的:属性、方法、构造器
<3>super的调用是逐层向上追溯的,并不仅仅局限于直接父类
3.2super调用属性和方法
<1>在子类的方法或构造器中,通过“super.属性”或“super.方法”显示调用父类声明的属性和方法,但一般省略;
<2>特别的,当子类和父类中定义了同名的属性或方法时,要想在子类中调用父类的属性或方法,应显示使用“super.属性”或“super.方法”。
3.3super调用构造器
<1>我们可以在子类的构造器中显示地使用“super(形参列表)”的方式,调用父类中声明的指定的构造器;
<2>“super(形参列表)”需在构造器首行;
<3>在类的构造器中,针对于"this(形参列表)"和“super(形参列表)”只能且必须二选一;
<4>在构造器首行,若没有显示声明“this(形参列表)”和“super(形参列表)”,则默认调用的是super(),即父类的空参构造器;
<5>当父类中没有空参的构造器时,子类中必须得有构造器显示使用“super(形参列表)”调用了父类中带有形参的构造器;若父类中没有空参构造器,子类中也不存在构造器显示的调用父类中带有形参的构造器,则会编译报错;
<6>在类的多个构造器中,至少有一个类的构造中使用“super(形参列表)”(可省略),调用父类中的构造器。
4.面向对象特征之三——多态性
4.1什么是多态性
父类的引用指向子类的对象
4.2多态的使用:虚拟方法调用
有了对象的多态性后,在编译期,只能调用父类中声明的方法;但在运行期,实际执行的是子类重写父类的方法。(编译看左边,运行看右边)
虚拟方法调用:子类中定义了与父类同名同参的方法,在多态情况下,将此时父类的方法成为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法在编译期是无法确定的。
eg:Person e=new Student();e.getInfo();
4.3多态性的使用前提
<1>要有类的继承关系;<2>要有方法的重写
4.4对象的多态性
多态只适用于方法,而不适用于属性。
5.向下转型的使用
5.1为何使用以及如何实现向下转型
为何使用:有了对象的多态性后,内存中实际上是加载了子类特有的属性和方法的,但由于变量声明为父类类型,导致编译时只能调用父类中声明的属性和方法,而子类中特有的属性和方法无法调用。只有通过向下转型的方法才能调用子类中特有的属性和方法。
如何实现:使用强转符号,格式为:(子类类型)对象
eg:Person p1=new Man();Man m1=(Man) p1;//则可以通过m1调用Man类中特有的方法和属性
5.2向下转型时需要注意的地方
强转时,可能出现ClassCastException的异常,为了避免该异常,在强转之前,先进行instanceof的判断,一旦为true,则进行向下转型;若返回false,不进行向下转型。
5.3instanceof的使用
<1>x instanceof A;x是否是类型的A的一个对象,返回类型为boolean类型;
<2>若B是A的父类,且x instanceof A返回true,则x instanceof B也必定返回true;
<3>想调用某个类中的方法,在new对象时,要么就new这个类,要么就new这个类的子类,不然内存中就不会有该类所对应的属性和方法;
eg:Person p=new SuperPerson();//Person是Man的父类,Man是SuperMan的父类
if( p instanceof Man){ //if语句会执行
Man m=(Man) p;
m.方法 //可调用Man类中特有的方法 }
<4>例题:
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 FieldMethodTest
{
public static void main(String[] args)
{
Sub s=new Sub();
System.out.println(s.count);//20
s.display();//20
Base b=s;//这是b和s指向同一个地址
System.out.println(b==s);//所以为true
System.out.println(b.count);//10,之所以是10,是因为要注意父类中属性count被子类Sub继承了,
// 所以这个b.count肯定是10啊。
// Sub类中的count是它特有的属性,因为对象的多态只适用于方法,不适用于属性,
// 所以要想输出20,必须进行强转操作。
b.display();//20
}
}
6.Object类的使用
6.1Object类的说明
<1>Object类是所有Java类的父类;
<2>Object类中的功能(属性、方法)具有通用性;
<3>Objcet类中只有一个空参的构造器。
6.2equals()的使用
<1>回顾“==”操作符的使用
1)可使用在基本数据类型和引用数据类型变量中;
2)若比较的是基本数据类型变量:比较两个变量保存的数据是否相等(不要求类型一定相等,但要能互相转化);
若比较的是引用数据类型变量:比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体。
<2>equals()方法的使用
1)运用于引用数据类型;
2)Object类中equals()的定义
public boolean equals(Object obj){
return (this==obj); } //说明这里的equals只是比较两个对象的地址值是否相同
3)像String、Date、File、包装类等都重写了equals()方法,重写后比较的不是地址,而比较的是两个对象的实体内容;
4)我们自定义类若使用equals(),通常也是比较两个对象的“实体内容”是否相同,那么就需要对Object类中的equals()进行重写。
<3>例题:
定义满足要求的MyDate类,在MyDate类中覆盖equals()方法,使其判断当两个MyDate类型对象的年月日都相同时,结果为true,否则为false。代码如下:
public class MyDate
{
String year;
String month;
String day;
public MyDate(String year, String month, String day)
{
this.year = year;
this.month = month;
this.day = day;
}
public boolean equals(Object o)
{
if(this==o) return true;
if(o==null || this.getClass()!=o.getClass()) return false;
MyDate m=(MyDate) o;
return this.year.equals(m.year)&&this.month.equals(m.month)&&this.day.equals(m.day);
}
}
6.3toString()的使用
<1>toString()方法在Object类中定义,其返回值为String类型,返回的是类名和它的引用地址;
<2>像String、Date、File、包装类等都重写了toString方法,使得在调用对象的toString()时,返回的是实体内部的信息;
<3>一般System.out.println(p)和System.out.println(p.toString)输出结果是一样的,除非p=null时,前者输出null,后者报NullPointerException异常。
7.单元测试
7.1单元测试的要求以及规定
<1>创建Java类进行单元测试时,要求该Java类:是public的;该类提供了公共的空参构造器;
<2>在声明单元测试方法时,方法的权限需为public,且无返回值无形参;
<3>单元测试方法上需要声明注解:@Test,双击注解,点并add JUint 4 to classpath或add JUint 5.4 to classpath ;并在类中导入:import org.junit.Test;
8.包装类(wrapper)
8.1包装类的定义
针对8种基本数据类型定义相应的引用类型,如此便可调用相应的方法
8.2基本数据类型,包装类,String之间的相互转换
<1>基本数据类型转换为包装类→装箱
之前是使用构造器(int a =11; Integer t=new Integer(a);)
现在直接用:Integer t=11 // 其他包装类也如此;
并且现在是自动装箱;
<2>包装类转换为基本数据类型→拆箱
自动拆箱(eg:in1为Integer类的对象,现在可以int num=in1);
<3>基本数据类型,包装类转换为String
方法一:使用+(加号)
eg:int num1=10; String str1=num+"";
方法二:使用valueOf()方法
eg:float f1=12.3f; String str2=String.valueOf(f1); //此时str2的值为字符串“12.3”
Double d1=13.4; String str3=String.valueOf(d1); //此时str2的值为字符串“13.4”,其实后面的一行代码也可以改为String str3=d1.toString;
<4>String转换为基本数据类型,包装类
使用parseXxx方法(Xxx为想要转成的类型)
eg:String str4="123"; int num2=Integer.parseInt(str4); //此时num2的值为整型123
8.3一些例题及其解释说明
<1>Object o1= true ? new Integer(1) : new Double(2.0);
System.out.println(o1); //输出结果为1.0
<2>Integer m=1;
Integer n=1;
System.out.println(m==n); //true
Integer x=128;
Integer y=128;
System.out.println(x==y); //false
之所以前者输出true,后者输入false,是因为Integer类内部定义了IntegerCache方法,IntegerCache内部定义了Integer[],保存了从-128~127范围内的整数,若我们使用自动装箱的方式,给Integer赋值的范围在-128~127范围内时,可以直接使用数组中的元素,不用再去new了,目的是提高效率,所以对于上面的代码,前者没有new对象,后者new了。
<3>利用Vector代替数组处理:从键盘读入学生成绩(以负数输入代表结束),找出最高分,并输出学生成绩等级。提示:给向量v添加元素:v.addElement(Object obj);取出向量v中的第i个元素:Object obj =v.elementAt(i)。
public class ScoreLevel
{
public static void main(String[] args)
{
Vector v=new Vector();
Scanner in =new Scanner(System.in);
int max=0;
while (true)
{//找出了最大值,向量中也一直在添加元素
int n1=in.nextInt();
if(max<n1) max=n1;
v.addElement(n1); //这里本来要用Object类型的元素或者其子类,本来要用Integer,这里直接用了int,是因为自动拆箱
if(n1<0)
break;
}
for(int i=0;i<v.size();i++)
{
if(v.elementAt(i) instanceof Integer)
{
//elementAt返回的类型是Object,所以这个强转必须要,然后又自动拆箱则能和max比较
//这里直接输出v.elementAt(i)倒是可以,但是如果比较时不强转,就会报它的类型为Object,
//与max类型不匹配,可以把参与计算等也看作是调用子类特有的方法,因为计算也是基于子类自身的性质来的
if (max - (Integer) v.elementAt(i) < 10)
System.out.println("第" + (i + 1) + "学生的成绩是A等");
else if (max - (Integer) v.elementAt(i) < 20)
System.out.println("第" + (i + 1) + "学生的成绩是B等");
else if (max - (Integer) v.elementAt(i) < 30)
System.out.println("第" + (i + 1) + "学生的成绩是C等");
else
System.out.println("第" + (i + 1) + "学生的成绩是D等");
}
}
}
}