一、 类的继承
面向对象的三大特性:
1.封装(封装细节)
2.继承
3.多态
继承是面向对象的重要概念,软件中的继承和现实中的继承概念是一样的。继承是实现软件可重用性的重要手段,如:A类继承B类,A类就拥有了B类的所有特性,如现实世界中的儿子继承父亲的财产,这就是重用性
在Java中只支持类的单继承,一个类只能继承一个类,直接的父类只有一个。一个类如果没有显示的继承其他类,则该类默认继承Object类, Object是sun提供的Java中的根类。
在Java中的继承使用extends关键字,语法格式:
[访问修饰符] class 子类extends 父类{ 类体; } |
1.1 Java支持单继承
C++支持多继承,可以继承多个类中的方法和属性,结果是造成代码逻辑复杂、思路混乱,一直备受争议。为了代码的更加清晰,Java取消了多继承,只支持单继承。继承最基本的作用是代码的重用。更重要的作用是实现多态。
第一个例子:Student类和Employee类的相同点和存在的问题
public class ExtendsTest01 {
public static void main(String[] args){
Student01 stu = new Student01(); stu.setStudentNo(1001); stu.setName("张三"); stu.setSex("男"); stu.setAddress("小店区");
Employee01 em = new Employee01(); em.setWorkNo(2002); em.setName("王五"); em.setSex("男"); em.setAddress("小店区");
System.out.println(stu.getStudentNo()); System.out.println(stu.getName()); System.out.println(stu.getSex()); System.out.println(stu.getAddress());
System.out.println(em.getWorkNo()); System.out.println(em.getName()); System.out.println(em.getSex()); System.out.println(em.getAddress()); } }
class Student01{ // 学生学号 private int studentNo; // 学生姓名 private String name; // 学生性别 private String sex; // 家庭住址 private String address;
public int getStudentNo() { return studentNo; }
public void setStudentNo(int studentNo) { this.studentNo = studentNo; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getSex() { return sex; }
public void setSex(String sex) { this.sex = sex; }
public String getAddress() { return address; }
public void setAddress(String address) { this.address = address; } }
class Employee01{ // 员工编号 private int workNo; // 员工姓名 private String name; // 员工性别 private String sex; // 家庭住址 private String address;
public int getWorkNo() { return workNo; }
public void setWorkNo(int workNo) { this.workNo = workNo; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getSex() { return sex; }
public void setSex(String sex) { this.sex = sex; }
public String getAddress() { return address; }
public void setAddress(String address) { this.address = address; } } |
在上面的例子中,学生类Student和员工类Employee的成员属性都重复出现了name、sex、address属性,可以从Student类和Employee类中抽象出一个父类Person,让学生和员工类中共有的属性作为person类的属性,让Student类和Employee类继承它,这样我们的冗余代码就会减少,同时,Person类还可以作为其他如Worker类的父类。
在Java中,Object类是所有类的父类。一个类如果没有显示的继承其他类,则该类默认继承Object类。那么Object类中所有的方法都可以继承过来,常用到的如:toString(),equals()等等。
Java继承图示:
如果一个类继承了一个父类,这个父类如果没有显示的继承其他类,那么这个父类的直接父类就是Object类。 所以,在Java中,所有的Java类都直接或间接的继承了Object类,都具有Object类的特性,如果一个方法的参数类型 是Object,那么表示参数可以是任何引用类型,因为所有的类,包括用户定义的类都是Object类的直接子类或间接子 类。
子类继承父类所有的属性和方法,包括成员方法、成员属性、静态方法和静态属性,但是注意:子类不继承父类的构造方法。
对于父类中声明的私有属性,子类要通过公共的方法进行访问,不能直接进行访问。对于父类中私有的成员方法和静态方法,子类无法访问。通常情况下,父类的这些私有方法是作为其他公共方法的工具方法,是不让外部直接访问,包括子类。
1.1 方法的重写override
为什么要对继承来的方法进行重写!
因为父类中的方法已经无法满足当前子类的业务需求,需要将父类中的方法进行重新实现。
发生方法覆盖的条件:
1.在具有继承关系的子类中进行方法重写。
2.子类中重写的方法必须与父类的方法具有相同的方法名,相同的返回值类型,相同的参数列表。
3.重写的方法不能比被重写的方法拥有更低的访问权限。
4.重写的方法不能比被重写的方法抛出更宽泛的异常。
5.私有的方法不能被覆盖。
6.构造方法无法被覆盖。因为构造方法无法被继承。
7.静态的方法不存在覆盖。
8.覆盖指的是成员方法,和成员变量无关。
继承最基本的作用:代码重用。 继承最重要的作用:方法可以重写。
1.1 多态
多态其实就是多种状态,实现的方式是译期绑定父类,也就是静态绑定(前期绑定),运行期间绑定子类,也叫动态绑定(后期绑定)。
多态存在的条件:
1.有继承
2.有覆盖
3.父类型指向子类型的引用
在Java语言中,子类和父类之间存在向上转型和向下转型。
1.向上转型(upcasting): 子类转型到父类,自动类型转换。
2.向下转型(downcasting): 父类转型到子类,强制类型转换。
注意:两个类之间必须要有继承关系,才存在向上转型还是向下转型。
向上类型转换:
SuperClass c1 = new SubClass(); |
向上转型又被称作自动类型转换。父类型SuperClass的引用指向子类型SubClass的对象。程序分两个阶段:编译阶段,运行阶段。在程序编译阶段只知道c1是一个SuperClass类型的引用,直到程序运行时才确定c1是SubClass类型。
c1.method(); |
程序在编译阶段c1被编译器看做SuperClass类型,所以程序在编译阶段c1引用绑定的是SuperClass类中的method()方法(静态绑定)。当程序在运行时堆中的对象实际上是SubClass类型,而SubClass已经重写了method()方法,所以程序在运行阶段c1对象绑定的方法是SubClass类中的method()方法(动态绑定)。
向下类型转型:
如果父类引用指向子类对象的一个实例要执行子类特有的方法时,就需要进行向下类型转换,因为在父类中找不到子类中特有的方法,必须进行向下类型转换,也称为强制类型转换,需要强制类型转换符,如下。
SuperClass c1 = new SubClass(); SubClass c2 = (SubClass)c1; c2.subMethod(); |
但是为了防止发生ClassCastException异常,通常情况下要先做类型判断,判断c1是否是SubClass类型,如果是同一类型才进行强制类型转换,否则不进行类型转换。
if(c1 instanceof SubClass){ SubClass c2 = (SubClass)c1; } |
使用多态可以使代码之间的耦合度降低。项目的扩展能力增强。尽量不要面向具体编程,面向父类型编程,面向抽象编程,面向接口编程。
1.1 super关键字
super关键字的作用:
1.调用父类的非私有构造方法。
2.调用父类的非私有成员方法和成员属性。
3.调用父类的非私有静态方法和静态属性。
需要注意:super 只能应用在成员方法和构造方法中,不能应用在静态方法中(和this 是一样的),如果在构造方法中使用必须放在第一行。
super不是引用类型,super中存储的不是内存地址,指向的不是父类对象,super代表的是当前子类对象中的父类型特征。
super关键字用在构造方法中:
1.当一个构造方法第一行如果没有this()也没有显示的去调用super()系统会默认调用super(),调用父类的无参数构造方法。
2.注意:super()的调用只能放在构造方法的第一行.
3.super()和this()不能共存。
4.super()调用了父类中的构造方法,但是并不会创建父类对象。通过子类的构造方法去调用父类的构造方法,作用是给当前子类对象中的父类型特征赋值。
5.在java语言中只要是创建Java对象,那么Object中的无参数构造方法一定会执行。
注意:单例模式的构造方法私有化,造成单例模式的类型无法被继承。因为子类不能调用父类的构造方法。
1.1 final关键字
final表示不可改变的含义
1.采用final 修饰的类不能被继承
2.采用final 修饰的方法不能被覆盖
3.采用final 修饰的局部变量一旦赋值不能被修改
4.final 修饰的成员属性必须显示初始化
5.如果修饰的引用,那么这个引用只能指向一个对象,也就是说这个引用不能再次赋值,但被指向的对象是可以修改的
6.构造方法不能被final 修饰
1.1 抽象类
如何定义抽象类!语法格式如下。
[访问修饰符] abstract class 类名{ 类体; } |
1.抽象类无法被实例化
2.虽然抽象类没有办法实例化,但是抽象类也有构造方法,该构造方法是给子类创建对象用的。
3.抽象类中可以定义抽象方法,在方法的修饰符列表中添加abstract关键字,并且抽象方法要以分号结束,不能带有方法体{}。
例如:
public abstract void m1(); |
5.抽象类中不一定有抽象方法,但抽象方法必须出现在抽象类中。
6.一个非抽象的类继承抽象类,必须将抽象类中的抽象方法覆盖,实现或重写。
7.抽象类不能被final修饰,抽象方法不能被final修饰。
1.2 接口
接口也是一种引用类型,定义接口的语法:
[访问修饰符] interface 接口名{ 常量; 抽象方法; } |
1.接口中可以存在:常量、抽象方法。
2.接口是一个特殊的抽象类,接口中所有的方法都是抽象的。
3.接口中没有构造方法,无法被实例化。
4.接口和接口之间可以多继承,一个接口可以继承多个接口,变相实现了多继承。
5.一个类可以实现多个接口,变相实现了多继承。
6.一个非抽象的类实现接口,必须将接口中所有的抽象方法都实现。
使用接口的好处:
1. 可以使项目同步开发,所有的组都面向接口开发,开发组面向接口中定义的方法进行实现。调用组按照接口定义的方法说明进行调用,而不必关心方法的具体实现。分组开发可以提高开发效率。
2. 接口使代码之间的耦合度降低,项目的扩展性好,使项目的各组件变得可插拔。实现多态。
如果接口和抽象类都能完成某个功能,优先选择接口。因为接口可以多继承。并且一个类除了实现接口之外,还可以去继承其他类。特殊的情况是,抽象类中其他的方法实现特定的功能,只有某个方法抽象,抽象的方法就是让子类重写的,但是其他的方法可以被子类调用已完成特定的功能。
1.1 多态
多态其实就是多种状态的含义,如我们方法重载,相同的方法名称可以完成不同的功能,这就是多态的一种表现,此时成为静态多态。另外就是我们将学生保存到数据的示例,当我们调用DBUtil接口中的方法,那么java 会自动调用实现类的方法,如果是Oracle 实现就调用Oracle 的方法,如果是MySql 实现就调用MySql 中的方法,这是在运行期决定的。
多态的条件是:有继承或实现,有方法覆盖或实现,父类对象(接口)指向子类对象或具体实现。
1.2 接口与抽象类的区别
1.接口描述了方法的特征,不给出实现,一方面解决java 的单继承问题,实现了强大的可接插性
2.抽象类提供了部分实现,抽象类是不能实例化的,抽象类的存在主要是可以把公共的代码移植到抽象类中
3.面向接口编程,而不要面向具体编程(面向抽象编程,而不要面向具体编程)
4.优先选择接口(因为继承抽象类后,此类将无法再继承,所以会丧失此类的灵活性)
1.3 Object类
Object类是所有Java 类的根类,如果在类的声明中未使用extends关键字指定继承某个父类,则默认的父类为Object 类。
public class User { 类体; } 相当于 public class User extends Object { 类体; } |
所以任何一个Java类都继承了Object类的所有方法。
1.1.1 toString()方法
toString()方法是Object类的方法,在Java中所有的类最终都继承了Object类,所以每个类都可以继承并使用Object类中所有的方法,当然也可以根据不同的需求对继承Object类的方法进行重写或覆盖。
例子:Object类的toString()方法的默认输出
public class ToStringTest01 {
public static void main(String[] args){
ToStringTest01 test = new ToStringTest01();
System.out.println(test.toString()); } } |
控制台显示:
ToStringTest01@6b97fd |
显示的内容包括三部分:完整的类名、@和十六进制的值,这个十六进制的值是这个对象的在堆内存中的地址。
Object类的toString()方法声明:
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } |
所有的类都继承了Object类,但是需要注意的是,sun提供的Java类很多都重写了继承来的toString()方法,如Boolean类、Double类等。
例子:sun提供的Java类大多数都重写了toString()方法
public class ToStringTest02{
public static void main(String[] args){
Integer i = new Integer(10); Double d = new Double(10.11); Float f = new Float(10.11); d = null;
System.out.println(i); System.out.println(d); System.out.println(f); } } |
控制台显示:
10 10.11 10.11 |
Integer类中重写的toString()方法:
public String toString() { return String.valueOf(value); } |
通常情况下,用户编写的类也继承了Object类的toString()方法,如果不进行重写,调用的就是父类Object的toString()方法,在上面的例子中我们已经做了演示。在某些情况下,toString()方法需要重写,因为Object类提供的toString()方法不是用户想要的结果,通过下面的例子进行说明。
例子:用户定义的类继承了toString()方法
public class ToStringTest03 {
public static void main(String[] args){
Student stu = new Student("张三",22);
System.out.println(stu.toString()); } }
class Student{
private String name;
private int age;
public Student(String name,int age){ this.name = name; this.age = age; } } |
控制台显示:
Student@1c78e57 |
上面例子的输出不是我们想要的,我们希望输出的是学生的姓名和年龄。在这种情况下就需要对父类Object的toString()方法进行重写,因为如果不重写toString()方法,调用的就是父类Object的toString()方法。实际上Object的toString()方法就是让子类根据不同情况进行重写的。修改上面的代码,重写toString()方法。
例子:重写Object类的toString()方法
public class ToStringTest04 {
public static void main(String[] args){
Student stu = new Student("张三",22);
System.out.println(stu.toString()); } }
class Student{
private String name;
private int age;
public Student(String name,int age){ this.name = name; this.age = age; } public String toString(){ return "学生姓名: [ " + name + " ] 年龄: [ " + age + " ]"; } } |
控制台显示:
学生姓名: [ 张三 ] 年龄: [ 22 ] |
1.1.2 equals()方法
equals()方法和toString()方法相同,也是Object类的方法,所有的子类都继承了父类的这个方法。下面先通过一个例子说明。
例子:EqualsTest01
public class EqualsTest01{
public static void main(String[] args){
User user1 = new User("张三","abc"); User user2 = new User("张三","abc");
System.out.println(user1.toString()); System.out.println(user2.toString());
boolean b = user1.equals(user2); System.out.println(b);
User user3 = user1; b = user1.equals(user3); System.out.println(b); // true } }
class User{
private String userName;
private String password;
public User(String userName,String password){ this.userName = userName; this.password = password; } } |
控制台显示:
User@1c78e57 User@5224ee false true |
从控制台的显示可以看出,输出的结果不是我们想要的。User类是Object类的子类,如果User类中没有重写equals()方法,那么调用的就是Object类的equals()方法,而Object类的equals()方法比较的是对象的内存地址而不是内容,所以,用户的类可以根据不同情况重写Object类的equals()方法。
Sun提供的Java类,常用到的类都重写了equals()方法,下面通过例子说明。
例子:sun提供的工具类都重写了equals()方法
public class EqualsTest02 {
public static void main(String[] args){
Integer i1 = new Integer(10); Integer i2 = new Integer(10);
Double d1 = new Double(10.11); Double d2 = new Double(10.11);
Float f1 = new Float(10.11); Float f2 = new Float(10.11);
System.out.println(i1.equals(i2)); System.out.println(d1.equals(d2)); System.out.println(f1.equals(f2)); } } |
控制台显示:
true true true |
从输出结果就可以看到,equals()方法比较的是内容而不是内存地址。
Object类的equals()方法:
public boolean equals(Object obj) { return (this == obj); } |
Integer类重写后的equals()方法:
public boolean equals(Object obj) { if (obj instanceof Integer) { return value == ((Integer)obj).intValue(); } return false; } |
下面重点说明String类的比较,也就是字符串的比较,在Java程序开发过程中使用最多的是字符串的比较。下面通过一个例子说明。
例子:String类也重写了equals()方法,比较的是内容
public class EqualsTest03 {
public static void main(String[] args){
String str1 = new String("abc"); String str2 = new String("abc");
System.out.println(str1); System.out.println(str1);
System.out.println(str1 == str2); System.out.println(str1.equals(str2)); } } |
控制台显示:
abc abc false true |
从上面的例子中可以看出,String类重写了Object的toString()方法。另外需要注意的是,在Java中==号如果是基本数据类型比较的是值,如果是引用类型比较的是内存地址,所以用==比较两个字符串返回的是false。注意:String类同样重写了Object累的的equals()方法,比较的不再是内存地址而是内容,所以,在Java中比较字符串使用的是String类的equals()方法。
用户的类也同样可以重新Object的equals方法,通常情况下这些类都是一些实体类。下面通过例子说明。
例子:用户定义的类继承了Object类的equals()方法,比较的是内存地址
public class EqualsTest04 {
public static void main(String[] args){
Worker worker1 = new Worker("张三","男"); Worker worker2 = new Worker("张三","男");
System.out.println(worker1.equals(worker2)); // false } }
class Worker{
private String name;
private String sex;
public Worker(String name,String sex){ this.name = name; this.sex = sex; } } |
控制台显示:
false |
根据需要,用户的类可以重写Object类的equals()方法,上面的例子中,只要姓名和性别相同,我们就认为是同一个人,所以,我们要重写equals()方法,修改上面的例子,在Worker类中重写equals方法。
例子:重写equals()方法
public class EqualsTest05 {
public static void main(String[] args){
Worker worker1 = new Worker("张三","男"); Worker worker2 = new Worker("张三","男");
System.out.println(worker1.equals(worker2)); } }
class Worker{
private String name;
private String sex;
public Worker(String name,String sex){ this.name = name; this.sex = sex; }
public boolean equals(Object o){ if (this == o){ return true; } if (o instanceof Worker) { Worker worker = (Worker)o; if(this.name.equals(worker.name) && this.sex.equals(worker.sex)){ return true; } } return false; } } |
控制台显示:
true |
1.1.3 finalize()方法
当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。子类可以重写finalize()方法,以配置系统资源或执行其他清除。
当 Java虚拟机已确定内存中的对象没有引用时,将调用此方法。
例子:重写finalize()方法
public class FinalizeTest01 {
public static void main(String[] args){
Manager manager = new Manager();
manager = null; // 启动垃圾回收机制 System.gc(); } }
class Manager{
private String name;
public void finalize()throws Throwable{
System.out.println("垃圾回收器正在回收垃圾!"); } } |
控制台显示:
垃圾回收器正在回收垃圾! |
finalize()方法是Object类的方法,在Object类中方法没有实现,实际上是让用户根据需要对方法进行重写,目的是释放资源或将内存中的对象重新救活。
此方法不是程序员调用的,是有垃圾回收器调用的,当内存的对象没有引用时,垃圾回收器会回收并释放空间,在回收垃圾前首先要调用finalize()方法,如果没有救活对象,将对象进行回收。垃圾回收器不定时的对内存中的垃圾对象进行释放,可以通过System类的gc()方法立即启动垃圾回收器对垃圾进行回收。
1.1.4 Java中的包机制和import
软件包机制:
1.为了解决类的命名冲突问题,在类名前加命名空间。
2.在java中使用package关键字定义包。
3.package语句只能出现在.java源文件的第一行。
4.package定义的格式,通常采用公司域名倒叙方式。格式:公司域名倒叙.项目名.模块名,例如:com.weixin.项目名.模块名
5.完整的类名是带有包名。
import语句的作用:
如果在当前包中创建的类中依赖其他的类,而这些类并不在当前包中,就需要使用import语句将这些类引入到当前文件中。语句格式如下。
import 完整的类名 ; |
import语句的位置只能出现在package语句之下,在类定义语句之上。
另外需要知道的是java.lang软件包下所有类不需要手动导入,系统会自动导入java.lang包下的所有类到当前文件中。
常用到的Java开发包。
java.lang:Java语言标准包,使用此包中的内容无需import导入。
java.sql:sun提供的JDBC接口类。
java.util:sun提供了常用工具类。
java.io:sun提供了各种输入输出流。
1.2 访问控制权限
在Java中,访问修饰符主要包括:private、protected、缺省和public,主要的目的是限制其他类对该类、属性和方法的使用权限。
修饰符 | 类的内部 | 同一个包里 | 不同包下子类 | 任何地方 |
private | YES | NO | NO | NO |
缺省 | YES | YES | NO | NO |
protected | YES | YES | YES | NO |
public | YES | YES | YES | YES |
注意:对类的修饰只有public和缺省,如:public class A{}、class B{}。
1.2.1 public修饰符
1. 修饰类
public修饰的类可以在任何包下都可以访问,但是如果在不同的包下,必须将类导入到Java文件中。
2. 修饰方法
如果一个类中使用public修饰的成员方法,需要使用引用.的方式进行访问。如果修饰的是静态方法需要使用类名.的方式进行访问。
3. 修饰属性
还可以修饰成员属性和静态属性,修饰的成员变量使用引用.的方式进行访问,静态属性使用类名.的方式访问。
1.2.2 private修饰符
1.不能修饰类
2.修饰方法
可以是成员方法和静态方法,修饰方法只能在本类中才可以访问
3.修饰变量(不是局部变量)
修饰的成员变量和静态变量只能在本类中才可以访问,通常情况下要提供公共的方法让外部可以间接的访问本类中的私有的属性。
1.1.1 protected修饰符
1. 不能修饰类
2. 修饰方法
修饰的方法可以在本类中访问,在本包下的外部类中和子类中才可以访问,同样适用引用.的方式进行访问,静态方法还是使用类名.的方式进行访问。
3. 修饰属性
不能修饰类和接口。protected修饰的属性只能在本类、同一个包下和子类可以访问。
1.1.2 缺省修饰符
1. 能修饰类
修饰的类只能在本包下可以访问
2. 修饰方法
缺省修饰符修饰的方法只能在本类或同一个包下的类才可以访问。
修饰属性
缺省修饰符修饰的属性只能在同一个包内才可以访问。