1、继承
继承就是在已经存在的类的基础上再进行扩展,从而产生新的类。已经存在的类称为父类、超类或基类,而新产生的类称为子类或派生类。
继承所表达的就是对象类之间的相交关系,它使得某类对象可以继承另外一类对象的数据成员和成员方法。若类B继承类A,则属于B的对象便具有类A的全部或部分性质(数据属性)和功能(操作),我们称被继承的类A为基类、父类或超类,而称继承类B为A的派生类或子类。
代码试例:汽车类{
汽车有四个轮子属性
引擎属性
方向盘属性
如何驾驶() //方法函数
如何保养() //方法函数
}
宝马厂家的敞篷跑车继承汽车类
{
如何打开敞篷 //方法函数
}
继承的类型
继承可以分为单继承和多继承。单继承是指一个子类最多只能有一个父类。多继承是一个子类可以有俩个以上的父类。Java语言中的类只支持单继承,多继承是通过接口来间接实现的。
派生类
Java中类的继承是通过extends来修饰的,通过extends的关键字表明前者具备后者的公共的成员变量和方法,再具备了所有公共的成员变量和方法后,本身还能定义一些特有的成员变量和方法。基本语法如下:
访问控制符 [修饰符] class 类名 extends 父类名{
.........
}
例题:定义一个学生类Student,继承自person类。
定义父类 person 如下:
public class person{
private String name;
private int age;
public void setName(String name){
this.name=name;
}
public String getName(){
return name;
}
public void setAge(String age){
this.age=age;
}
public int getAge(){
return Age;
}
}
定义子类 Student 类如下:
public class Student extends Person{
private String school;
public void setSchool(String school){
this.school=school;
}
public String getSchool(){
return school;
}
public static void main(String[] args){
Student st=new student();
st.setName("奚佳欣");
st.setAge(20);
st.stSchool("西安欧亚学院");
systen.out.println("姓名: "+st.getName()+" 年龄: "+st.getAge()+" 学校: "+st.getSchool());
}
}
以上程序中的Student类扩充了Person类,增加了学校的属性及对应的setter和getter方法,由于Person类中已经存在name和age俩个属性,也就是说此时student类中已经存在了3个属性及3组setter和getter方法,所以在Student类中不需要重新声明着俩个属性,这时就需要考虑是否可以将person类中的内容继续保留到Student类中,也就是继承。
使用super调用父类的构造方法
子类如果想使用父类的构造方法,必须在子类的构造方法中使用,并且必须使用关键字super来表示,而且super必须是子类构造方法中第一个语句。
例题:修改上个例题,分别加入带参数的构造函数
定义父类Person1类:
public class Peson1{
private String name;
private int age;
public Personq(String name ,int age){
this.name = name;
this.age = age;
System.out.println("我是"+this.name);
System.out.println("我的年龄是"+this.age);
}
}
定义子类Student1类:
public class student1 extends Person1{
private String school;
public Student(String name, int age,String school){
super("奚佳欣",20);
this.school = school;
system.out.println("我来自"+this.school);
}
public static vold main(String[] ages){
Students st=new Student1("奚佳欣",20,"西安欧亚学院");
}
}
super用法总结:
1.在子类构造方法中要调用父类的构造方法,用"super(参数列表)"的方式调用,参数不是必须的。同时还要注意的一点是:"super(参数列表)"这条语句只能用在字参数构造方法中的第一行。
2.当子类方法中的局部变量或者子类的成员变量与父类变量同名是,也就是子类局部变量父类成员变量事,用“super.成员变量名”来引用父类成员变量。当然,如果父类的成员变量没有被覆盖,也可以用“super.成员变量名”来引用父类成员变量,但这样是不必要的。
3.当子类的成员方法覆盖了父类的成员方法时,也就是子类和父类有完全相同的方法定义(但方法可以不同),此时,用“super.方法名(参数列表)”的方式访问父类的方法。
4.super关键字只能用在类体中非静态部分,如构造函数与成员方法中,若在main()函数中调用或用在静态方法中则会编译出错,报出Cannot use super in a static context 的错误。
泛型数组
1、Java的数组允许在运行时确定数组的大小,但要改变数组的大小就不容易了,解决办法是使用ArrayList类,它是一个采用类型参数的泛型类,如:ArrayList<Employee>,尖括号里的是类名,表示这个数组列表类里存储的元素是Employee类的对象,如下声明一个保存Employee对象的数组列表:
ArrayList<Employee> staff = new ArrayList<Employee>();
2、操作数组列表
① 使用add方法可以将元素添加到数组列表中,如:staff.add(new Employee(“Tom”,...))会添加元素到末尾,如:staff.add(i, e)会添加元素到第i个位置,后面的元素向后移一位
② 使用remove方法删除一个元素,如:
1)staff.remove(1) //表示删除标号为1的元素,并返回被删除的那个元素
2)staff.remove(e) //其中e是一个对象的引用,如果在列表中找到e对象,删除它并返回true,找不到则返回false
③ 可以使用for each循环对数组列表遍历,如:for(Employee e : staff){...}
④ 如果已经清楚或能够估计出数组可能存储的元素数量,就可以在填充数组之前调用ensureCapacity方法,如:staff.ensureCapacity(100)或ArrayList<Employee> staff = new ArrayList<Employee>(100)
—— 这个方法和数组的大小有很重要的区别:如果数组分配100个元素的存储空间,数组就有100个空位置可以使用,而容量为100个元素的数组列表只是拥有保存100个元素的潜力,在最初,甚至完成初始化构造之后,数组列表根本就不含有任何元素,此时 数组列表.size() 等于0
⑤ size方法将返回数组列表中包含的实际元素数目,如:staff.size()
⑥ 一旦能够确定数组列表的大小不再发生改变,就可以调用trimToSize方法,这个方法将存储区域的大小调整为当前元素数量所需要的存储空间数目,垃圾回收器将回收多余的存储空间
—— 一旦整理了数组列表的大小,添加新元素就需要花时间再次移动存储块,所以应该在确认不会再添加任何元素时再调用trimToSize
⑦ 对于数组列表变量,如a和b,对于语句:a = b,表示让a和b引用同一个数组列表,并没有发生列表里元素的复制,这点和C++不同
3、访问数组列表元素
① 要设置第i个元素(注:数组列表的下标从0开始),可以:staff.set(i, harry)
—— 只有i小于数组列表大小时才能调用list.set(i,x),例如初始化了一个有存储100个元素潜力的数组列表,此时它的大小为0,所以list.set(0, x)这句是错误的,应使用add方法为数组列表添加新元素,而不要使用set方法,它只负责替换数组中已经存在的元素内容
② 要获得第i个元素,可以:staff.get(i)
③ 想要像数组那样访问数组列表元素,可以使用toArray方法将数组列表元素拷贝到一个数组中:list.toArray(a)
对象包装器与自动打包
1、Java为所有的基本类型提供了相对应的类,称为包装器(Integer、Long、Float、Double、Short、Byte、Character、Void、Boolean(前六个派生于公共的超类Number)),包装器是不可变的,不允许更改包装在其中的值,包装器还是final的,因此不能定义它们的子类,如对于数组列表,这样写是不允许的:ArrayList<int>,需要写成:ArrayList<Integer> list = new ArrayList<Integer>()
—— ArrayList<Integer>的效率远远低于int[]数组,所以此时数组列表更适合构造小型集合,因为这样操作起来更方便
2、自动打包
① 如果把int变量赋给Integer对象,Java会自动打包,如:list.add(3) 会自动换成 list.add(new Integer(3))
② 如果把一个Integer对象赋给int值时,会自动拆包,如:Integer n = 3; n++;等会自动拆开对象包,然后进行计算,最后将结果存入对象包内
—— 注意应避免使用 == 符合来判断两个包装器对象是否相等,因为这有可能是检测的是对象是否指向同一个存储区域,应该使用equals方法来进行比较
—— 打包和拆包是编译器认可的,而不是虚拟机
参数数量可变的方法
使用“...”符号来定义可变参数,如printf方法的定义是:public PrintStream printf(String fmt, Object... args){ },用户自己也可以定义可变参数方法,并将参数指定为任意类型,甚至是基本类型,如下面的代码计算若干个数值的最大值:
public static double max(double... values){
double largest = Double.MIN_VALUE;
for(double v : values)
if(v > largest) largest = v;
return largest;
}
—— 注意,虽然“...”符号在运行程序时类似于把参数变为数组,但并不等价
① 如上面的max方法的参数是“double[] d”,那么在调用max方法应该这样:double d = new double[]{1.0, 2.0}; max(d);或者max(new double[]{1.0, 2.2, 4,3});
② 如果max的参数是“double... values”,那么调用max方法应该这样:max(1.2, 1.3, 2.3);
枚举类
枚举类型(enum type)是指由一组固定的常量组成合法的类型。Java中由关键字enum来定义一个枚举类型。下面就是java枚举类型的定义。
publicenumSeason {
SPRING, SUMMER, AUTUMN, WINER;
}
特点
Java定义枚举类型的语句很简约。它有以下特点:
1) 使用关键字enum 2) 类型名称,比如这里的Season 3) 一串允许的值,比如上面定义的春夏秋冬四季 4) 枚举可以单独定义在一个文件中,也可以嵌在其它Java类中。
除了这样的基本要求外,用户还有一些其他选择
5) 枚举可以实现一个或多个接口(Interface) 6) 可以定义新的变量 7) 可以定义新的方法 8) 可以定义根据具体枚举值而相异的类
用法
反射
在运行状态中,对于任意一个类,都能够获取到这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有的方法和属性),这种动态获取的信息以及动态调用对象的方法的功能就称为java语言的反射机制。通俗点讲,通过反射,该类对我们来说是完全透明的,想要获取任何东西都可以。
想要使用反射机制,就必须要先获取到该类的字节码文件对象(.class),通过字节码文件对象,就能够通过该类中的方法获取到我们想要的所有信息(方法,属性,类名,父类名,实现的所有接口等等),每一个类对应着一个字节码文件也就对应着一个Class类型的对象,也就是字节码文件对象。
获取字节码文件对象的三种方式。
1、Class clazz1 = Class.forName("全限定类名"); //通过Class类中的静态方法forName,直接获取到一个类的字节码文件对象,此时该类还是源文件阶段,并没有变为字节码文件。
2、Class clazz2 = Person.class; //当类被加载成.class文件时,此时Person类变成了.class,在获取该字节码文件对象,也就是获取自己, 该类处于字节码阶段。
3、Class clazz3 = p.getClass(); //通过类的实例获取该类的字节码文件对象,该类处于创建对象阶段
继承设计的技巧
(1)将公共操作和域放置在超类
(2)不要使用受保护的域
有些程序员认为,将大多数的实例域定义为protected是一个不错的主意,只有这样,子类才能够在需要的时候直接访问他们。然而,protected 机制并不能够带来更好的保护,其原因主要有两点。第一,子类集合是无限制的,任何一个人都能够由某个类派生一个子类,并编写代码以直接访问 protected的实例域,从而破坏了封装性。第二,在Java程序设计语言中,在同一个包中的所有类都可以访问protected域,而不管它是否为 这个类的子类。
(3)使用继承实现“is-a”关系
使用继承很容易得到节省代码的目的,但有时候也被人们滥用了。例如,假设需要定义一个钟点工(Contractor)类。钟点工的信息包含姓名和雇佣日 期,但是没有薪水。他们按小时计薪,并且不会因为拖延时间而获得加薪。这似乎在诱导人们由Employee派生出子类Constractor,然后再增加 一个hourlyWage域。
class Contractor extends Employee
{
….
private double hourlyWage;
}
这并不是一个好主意。因为这样一来,每个钟点工对象中都包含了薪水和计时工资这两个域。在实现打印支票或税单方法的时候,会到来无尽的麻烦,并且会多些很多代码。
钟点工与雇员之间不属于“is-a”关系。钟点工不是特殊的雇员。
(4)除非所有继承的方法都有意义,否则不要使用继承。
假设想编写一个Holiday类。毫无疑问,每个假日也是一日,并且一日可以用GregorianCalendar类的实例表示,因此可以使用继承。
class Holiday extends GregorianCalendar
{
………….
}
很遗憾,在继承的操作中,假日集不是封闭的。在GregorianCalendar中有一个共有方法add,可以将假日转换成非假日:
Holiday Christmas;
christmas.add(Calendar.DAY_OF_MONTH,12);
因此,继承对于这个例子来时并不太适宜。
(5)在覆盖方法的时候,不要改变预期的行为。
置换原则不仅应用于语法,而且也可以应用于行为,这似乎更加重要。在覆盖一个方法的时候,不应该毫无缘由的改变行为的内涵。就这一点而言,编译器不会提供 任何帮助,即编译器不会检查重定义的方法是否有意义。例如,可以重定义Holiday类中的add方法“修正”原方法的问题,或什么也不做,或抛出一个异 常,或继续到下一个假日。然而这些都违反了置换原则,语句序列
int d1=x.get(Calendar.DAY_OF_MONTH);
x.add(Calendar.DAY_OF_MONTH,1);
int d2=x.get(Calendar.DAY_OF_MONTH);
System.out.println(d2-d1);
不管x属于GregorianCalendar类,还是属于Holiday类,执行上述语句后都应该得到预期的行为。
当然,这样可能会引起某些争议。人们可能就预期行为的含义争论不休。例如,有些人争论说,置换原则要求Manager.equals不处理bonus域, 因为Employee.equals没有它。实际上,凭空讨论这些问题毫无意义。关键在于,在覆盖子类中的方法时,不要偏离最初的实际想法。
(6)使用多态,而非类型信息。
无论什么时候,对于下面这种形式的代码:
if(x is of type1)
action1(x);
else if (x is of type2)
action2(x)
都应该考虑使用多态性。
action1与 action2表示的是相同的概念吗?如果是相同的概念,就应该为这个概念定义一个方法,并将其放置在两个类的超类或接口中,然后,就可以调用x.action( );以便使用多态性提供的动态分派机制执行相应的动作。
使用多态犯法或接口编写的代码比使用对多种类型进行检测的代码更加易于为何和扩展。
(7)不要过多地使用反射
反射机制使得人们可以通过在运行时查看域和方法,让人们编写出更具有通行的程序。这种功能对于编写系统程序来说及其实用,但是通常不是用于编写应用程序。反射是很脆弱的,即编译器很难帮助人们发现程序中的错误。任何错误只能在运行时才被发现,并导致异常。