java基础进阶——继承、多态、异常捕获(2)

本文章学习《java核心技术卷1》做的笔记,希望对大家有帮助。

本篇文章将学习面向对象程序设计的另外一个基本概念:继承。

继承的基本思想是,可以基于已有的类创建新的类,继承已存在的类就是复用(继承)这些类的方法,而且可以增加一些新的方法和字段,使新类能够适应新的情况。

反射:反射是指在程序运行期间更多地了解类及其属性的能力。反射是一个功能强大的特性。

一、类、超类和子类

1、定义子类

接着上一篇java基础进阶的文章,继续使用之前讨论过的Employee类,假设你在某个公司工作,这个公司里经理的待遇与普通员工的待遇存在着一些差异。不过,他们之间也存在着很多相同的地方,例如,他们都领薪水,只是普通员工在完成本职任务之后仅领取薪水,而经理在完成了预期的业绩之后还能得到奖金。这种情形就需要使用继承。

可以如下继承Employee类来定义manager类,这类使用关键字extends表示继承。

实例:

public class Manager extends Employee

{

added methods and fields //添加新增的方法或代码块

}

**关键字extends表明正在构造的新类派生于一个已存在的类。**这个已存在的类称为超类(superclass),基类(base class)或父类(parent class);新类称为子类(subclass),派生类(derived class)或孩子类(child class)。 超类和子类是java程序员最常用的两个术语。

注意:

子类拥有的功能往往比超类多。

通过扩展超类定义子类的时候,只需要指出子类与超类的不同之处。因此在设计类的时候应该将最一般的方法放在超类中,而将更特殊的方法放在子类中,这种将通用功能抽取到超类的做法在面向对象程序设计中十分普遍。(在扩展超类定义子类的时候,虽然子类中没有定义父类的方法,但是子类依旧可以直接使用父类的方法)

2、覆盖方法

超类中的有些方法对子类并不一定适用。比如:超类与汽车相关,其中printInfo方法实现打印汽车的厂商、价格等。现在子类与新能源汽车相关,其中printInfo方法实现打印汽车的厂商、价格、续航等。因此需要提供一个新方法覆盖(override)超类中的这个方法。

我们希望调用超类中的方法,可以使用特殊的关键字super解决这个问题。

实例如下:(举例是一个工资加提成问题)

public double getSalary()

{

double baseSalary =super.getSalary();

return baseSalary +bonus;

}

注意:

在子类中可以增加字段、增加方法或覆盖超类的方法,不过,继承绝对不会删除任何字段或方法。

3、子类构造器

**由于Manager类的构造器不能访问Employee类的私有字段,所以必须通过一个构造器来初始化这些私有字段。可以利用特殊的super语法调用这个构造器。**使用super调用构造器的语句必须是子类构造器的第一条语句。

具体实例如下:(因为public class Manager extends Employee可知继承了Employee类,所以super会到Employee中找对应的构造器。)

public Manager(String name,double salary,int year,int month,int day)

{

super(name,salary,year,month,day); //调用Employee中带有n,s,year,month和day参数的构造器。

bonus=0; //默认奖金为0

}

如果子类的构造器没有显示地调用超类的构造器,将自动地调用超类的无参数构造器。如果超类没有无参数的构造器,并且在子类的构造器中又没有显示地调用超类的其他构造器,java编译器就会报错。

总结:

关键字this有两个含义:一是只是隐式参数的引用,二是调用该类的其他构造器。

关键super也有两个含义:一是调用超类的方法,而是调用超类的构造器。

一个对象变量可以指示多种实际类型的现象称为多态。在运行时能够自动地选择适当的方法,称为动态绑定。

4、继承层次

继承并不仅限于一个层次。例如,可以有Manager类派生Executive类。由一个公共超类派生出来的所有类的集合称为继承层次。在继承层次中,从某个特定的类到其祖先的路径称为该类的继承链。

5、多态

由一个简单规则可以用来判断是否应该将数据设计为继承关系,就是"is-a"规则,它指出子类的每个对象也是超类的对象。例如:每个经理都是员工,因此将Manager类设计为Employee类的子类是有道理的。反之则不然,并不是每个员工都是经理。

"is-a"规则的另一种表述是替换原则。它指出程序中出现超类(父类)对象的任何地方都可以使用子类对象替换。(通俗解释:子类可以访问父类的所有字段和方法)

在java程序设计语言中,对象变量是多态的。一个Employee类型的变量既可以引用一个Employee类型的对象,也可以引用Employee类的任何一个子类的对象。

6、阻止继承:final类和方法

有时候,我们可能希望阻止人们利用某个类定义子类。不允许扩展的类被称为final类。如果在定义类的时候使用了final修饰符就表明这个类是final类。

例如,假设希望阻止人们派生Executive类的子类,就可以在声明这个类的时候使用final修饰符。声明格式如下所示:

public final class Executive extends Manager

{

............

}

类中的某个特定方法也可以被声明为final。即:子类就不能覆盖这个方法(final类中的所有方法自动地成为final方法)

语法如下:

public class Employee

{

public final String getName()

{

return name;

}

................

}

复习:

前文曾经说过,字段也可以声明为final,对于final字段来说,构造对象之后就不允许改变它们的值了。不过,如果将一个类声明为final,只有其中的方法自动地成为final,而不包括字段。

7、强制类型转换

将一个类型强制转换成另外一个类型的过程称为强制类型转换。有时候需要将某个类的对象引用转换成另外一个类的对象引用。要完成对象引用的强制类型转换,转换语法与数值表达式的强制类型转换类似,仅需要用一对圆括号将目标类名括起来,并放置在需要转换的对象引用之前就可以了。

实例如下:

Manager boss=(Manager) staff[0];

进行强制类型转换的唯一原因是:要暂时忽视对象的实际类型之后使用对象的全部功能。

8、抽象类

如果自下而上(子类--->父类方向)在类的继承层次结构中上移,位于上层的类更具有一般性,可能更加抽象。从某种角度看,祖先类更有一般性,人们只将它作为派生其他类的基类,而不是用来构造你想使用的特定的实例。

例如:考虑扩展Employee类层次结构,员工是一个人,学生也是一个人,我们可以扩展类层次结构来加入类person和类Student。

抽象方法充当着占位方法的角色,它们在子类中具体实现,扩展抽象类可以有两种选择:

一种是在子类中保留抽象类中的部分或所有抽象方法仍未定义,这样必须将子类也标记为抽象类;

另一种做法是定义全部方法,这样一来,子类就不是抽象的了。

抽象类定义语法:(使用abstract修饰)

public abstract class Person

{

public abstract String getDescription();

...........

}

为了提高程序的清晰度,包含一个或多个抽象方法的类本身必须被声明为抽象的。

注意:

如果将一个类声明为abstract,就不能创建这个类的对象。

9、受保护访问

**前文提到,最好将类中的字段标记为private,而方法标记为public。**任何声明为private的内容对其他类都是不可见的。这对于子类来说完全适用,即子类也不能访问超类的私有字段。

在有些时候,程序员希望限制超类中的某个方法只允许子类访问,或者更少见地,可能希望允许子类的方法访问超类的某个字段。因此,需要将这些类方法或字段声明为受保护(protected)。

例如:

如果将超类Employee中的hireDay字段声明为proteced,而不是private,Manager方法就可以直接访问这个字段。

在java中,保护字段只能由同一个包中的类访问。 例如:现在考虑一个Administrotor子类,这个子类在另一个不同的包中。Administrator类中的方法只能查看Administrator对象自己的hireDay字段,而不能查看其他Employe对象的这个字段。有了这个限制,就能避免滥用保护机制,不能通过派生子类来访问受保护的字段,

在实际应用中,要谨慎使用受保护字段,假设你的类要提供给其他程序员使用,而你在设计这个类时设置了一些受保护的字段。其他程序员可能会由这个类派生出子类,从而访问你的受保护字段。在这种情况下,如果你想修改你的类的实现,就势必会影响哪些程序员。

受保护的方法更具有实际意义。如果需要限制某个方法的使用,就可以将它声明为protected。这表明子类(可能熟悉父类)得到了信任,可以正确地使用这个方法,而其他类则不行。

总结------java中4个访问控制修饰符:

仅对本类可见------private;

对外部完全可见------public;

对本包和所有子类可见------protected。

对本包可见------默认,不需要修饰符。

二、Object:所有类的超类

Object类是java中所有类的始祖,在java中每个类都扩展了Object,但是并不需要写成:

pbulic class Employee extends Object

如果没有明确地指出超类,Object就被认为是这个类的超类。由于java中每个类都是由Object类扩展而来的,所以,熟悉这个类提供的所有服务十分重要。

1、Object类型的变量

可以使用Object类型的变量引用任何类型的对象:

实例:

Object obj=new Employee("xiaohu",20000);

当然,Object类型的变量只能用于作为各种值的一个泛型容器,要想对其中的内容进行具体的操作,还需要清楚对象的原始类型,并进行相应的强制类型转换。

实例:

Employee e=(Employee) obj;

在java中,只有基本类型(primitive type)不是对象,例如,数值、字符和布尔类型的值不是对象。所有的数组类型,不管是对象数组还是基本类型的数组都扩展了Object类。

2、equals方法

Object类中的equals方法用于检测一个对象是否等于另一个对象。Object类中实现的equals方法将确定两个对象引用是否相等。

getclass方法将返回一个对象所属的类。

在子类中定义equals方法时,首先调用超类的equals,如果检测失败,对象就不可能相等。

3、hashCode方法

散列码(hash code)是由对象导出的一个整型值。散列值是没有规律的。

4、toString方法

它会返回表示对象值的一个字符串。

三、泛型数组列表

在程序中会遇到一个问题,定义好数组大小后,运行时数组不能动态的更改数组的问题,即一旦确定了数组的大小,就不能容易地改变它了。在java中,解决这个问题最简单的方法就是使用java中的另外一个类,名为ArrayList。ArrayList类类似于数组,但在添加或删除元素时,它能够自动地调用数组容量,而不需要为此编写任何代码。

ArrayList是一个有类型参数的泛型类。为了指定数组列表保存的元素对象的类型,需要用一对尖括号将类名括起来追加到ArrayList后面。

1、声明数组列表

声明和构造一个保存Employee对象的数组列表:

ArrayList<Employee> staff= new ArrayList<Employee>();

在java10中,最好使用var关键字以避免重复写类名:

var staff =new ArrayList<Employee>();

如果没有使用var关键字,可以省去右边的类型参数:

ArrayList<Employee> staff=new ArrayList<>();

这称为"菱形"语法,因为空尖括号<>就像是一个菱形。可以结合new操作符使用菱形语法。编译器会检查新值要做什么。如果赋值给一个变量,或传递给某个方法,或者从某个方法返回,编译器会检查这个变量、参数或方法的泛型类型,然后将这个类型放在<>中。

解释:

ArrayList<Employee> staff=new ArrayList<>(); 在这个例子中,new ArrayList()将赋值给一个类型为ArrayList<Employee>的变量,所以泛型类型为Employee.

使用add方法可以将元素添加到数组列表中。

实例如下:

staff.add(new Employee("xiaohu",.............));

staff.add(new Employee("xiaohu",.............));

解释:

**数组列表管理着一个内部的对象引用数组。**最终,这个数组的空间有可能全部用尽。这时就显现出数组列表的魅力:如果调用add而内部数组已经满了,数组列表就会自动地创建一个更大的数组,并将所有的对象从较小的数组中拷贝到较大的数组中。

如果已经知道或能够估计出数组可能存储的元素数量,就可以在填充数组之前调用ensureCapacity方法"

staff.ensureCapacity(100); //分配一个包含100个对象的内部数组

另外,可以把初始容量传递给ArrayList构造器:

ArrayList <Employee> staff=new ArrayList<>(100);

size方法将返回数组列表中包含的实际元素个数。

实例:

staff.size()

2、访问数组列表元素

数组列表自动扩展容量的便利增加了访问元素语法的复杂程度。因为ArrayList类并不是java程序设计语言的一部分;它知识由某个人编写并在标准库中提供的一个实用工具类。

不能使用我们熟悉的[]语法格式访问或改变数组的元素,而要使用get和set方法。

设置/获取第i个元素的实例:

staff.set(i,harry); //将staff泛型数组的第i个元素,设置为harry

Employee e=staff.get(i) //获取staff数组列表中的元素

staff.add(int index,E obj); //将obj插入到指定索引位置

staff.remove(int index); //删除指定索引为止的元素,并将后面的所有元素前移,返回所删除的元素

四、参数数量可变的方法

可以提供参数数量可变的方法(有是这些方法被称为"变参方法")。

实例------printf方法:

public class PrintStream

{

public PrintStream printf(String fmt,Object... args)

{

return format(fmt,args);

}

}

解释:

这里的省略号...是java代码的一部分,它表明这个方法可以接受任意数量的对象。

实际上,printf方法接收两个参数,一个是格式字符串,另一个是Object[]数组,其中保存着所有其他参数(如果调用者提供的是整数或者其他基本类型的值,会把他们自动装箱为对象)。现在不可避免地要扫描fmt字符串,并将第i个格式说明符与arg[i]的值匹配起来。

换句话说,对于printf的实现者来说,Object...参数类型与Object[]完全一样。

编译器需要转换每个printf调用,将参数绑定到数组中,并在必要的时候进行自动装箱:

System.out.printf("%d %s",new Object[] {new Integer(n),"widgets"});

五、接口

接口(interface),接口用来描述类应该做什么,而不指定它们具体应该如何做。

1、接口的概念

在java程序设计语言中,接口不是类,而是对希望符合这个接口的类的一组需求。

实例:Arrays类中的sort方法承诺可以对对象数组进行排序,但要求满足下面这个条件:对象所属的类必须实现Comparable接口。

以下是Comparable接口的代码:

public interface Comparable

{

int compareTo(Object other);

}

这说明,任何实现Comparable接口的类都需要包含compareTo方法,这个方法有一个Object参数,并且返回一个整数。接口中的所有方法都自动是public方法。因此,在接口中声明方法时,不必提供关键字public。

接口绝不会有实例字段,提供实例字段和方法实现的任务应该由实现接口的那个类来完成。因此,可以将接口看成是没有实力字段的抽象类。

为了让类实现一个接口,通常需要完成下面两个步骤:

将类声明为实现给定的接口;

对接口中的所有方法提供定义;

要将类声明为实现某个接口,需要使用关键字implements;

实例:

class Employee implements Comparabel; //表明Employee类实现Comparabel接口

2、接口的属性

接口不是类,具体来说,不能使用new运算符实例化一个接口。但可以声明接口的变量。接口变量必须引用实现了这个接口的类对象:

实例:

Comparable x;

x=new Employee(...)

接下来,使用instanceof检查一个对象是否属于某个特定类,可以使用instanceof检查一个对象是否实现了某个特定的接口。

实例:

if (anObject instanceof Comparable) {....}

与建立类的继承层次一样,也可以扩展接口。这里运行有多条接口链,从通用型较高的接口扩展到专用型较高的接口。

实例:假设有一个名为Moveable的接口:

public interface Moveable

{

void move(double x,double y);

}

然后可以假设一个名为Powered的接口扩展了以上Moveable接口:

public interface Powered extends Moveable

{

double milesPerGallon();

double number=15; //虽然在接口中不能包含实例字段,但是可以包含常量。

}

注意:

与接口中的方法都自动被设置为public一样,接口中的字段总是public static final。有些接口只定义常量,而没有定义方法。

尽管每个类只能有一个超类,但却可以实现多个接口,这就为定义类的行为提供了极大的灵活性。

比如:java程序设计语言有一个非常重要的内置接口,名为Cloneable。如果某个类实现了这个Cloneable接口,Object类中的clone方法就可以创建你的类对象的一个准确副本。如果希望自己设计的类拥有克隆和比较的能力,只要实现这两个接口就可以。可以使用逗号将想要实现的各个接口分隔开。

实例:

class Employee implements Cloneable,Comparable

3、接口与抽象类

使用抽象类表示通用属性存在一个严重的问题。每个类只能扩展一个类。但每个类可以实现多个接口 。

4、静态和私有方法

**在java8中,允许在接口中增加静态方法。目前为止,通常的做法都是将静态方法放在伴随类中。**在标准库中,你会看到成对出现的接口是实用工具类。如Collection/Collections或Path/Paths。

5、默认方法

可以为接口方法提供一个默认实现,必须使用default修饰符标记这样一个方法。

实例:

public interface Comparable<T>

{

default int compareTo(T other) {return 0;} //默认情况下,所有参数都是一样的

}

6、接口与回调

回调是一种常见的程序设计模式。在这种模式中,可以指定某个特定事件发生时应该采取的动作。

在java.Swing包中有一个Timer类,如果希望经过一定时间间隔就得到通知,Timer类就很有用。

7、对象克隆

我们将讨论Cloneable接口吗,这个接口指示一个类提供了一个安全clone方法。

六、异常、断言和日志

1、异常分类

在java程序设计语言中,异常对象都是派生于Throwable类的一个类实例。如果java中内置的异常类不能满足需求,用户还可以创建自己的异常类。

需要注意的是,所有的异常都是由Throwable继承而来,但在下一层立即分解为两个分支:Error和Exception。

Error类层次接口描述了java运行时系统的内部错误和资源耗尽错误。

在设计java程序时,要重点关注Exception层次结构。这个层次结构又分为两个分支:一个分支派生于RuntimeException;另一个分支包含其他异常。一般规则是:由编程错误导致的异常属于RuntimeException;如果程序本身没有问题,但由于像I/O错误这类问题导致的异常属于其他异常。

java语言规范将派生于Error类或RuntimeException类的所有异常称为非检查型异常,所有其他的异常称为检查型异常。

2、声明检查型异常

如果遇到了无法处理的情况,java方法可以抛出一个异常。这个原理很简单:方法不仅需要告诉编译器将要返回什么值,还要告诉编译器有可能发生什么错误。如果没有处理器捕获这个异常,当前执行的线程就会终止。

例如:一段读取文件的代码知道有可能读取的文件不存在,或者文件内容为空。因此,视图处理文件信息的代码就需要通知编译器可能会抛出IOException类异常。

实例:

public FileInputStream(String name) throws FileNotFoundException

解释:

这个声明表示这个构造器将根据给定的String参数产生一个FileInputStream对象,但也有可能出错而抛出一个FileNotFoundExceptoin异常。如果发生了这种糟糕情况,构造器将不会初始化一个新的FileInputStream对象,而是抛出一个FileNotFoundExcetion类对象。

如果一个方法有可能抛出多个检查型异常类型,那么就必须在方法的首部列出所有的异常类。每个异常类之间用逗号隔开。

实例:

class MyAnimation

{

public Image LoadImage(String s) throws FileNotFoundException,EOFException

{

}

}

3、创建异常类

你的代码可能遇到任何标准异常类都无法描述清楚的问题。在这种情况下,常见自己的异常类就是一件顺理成章的事情了。我们需要做的是定义一个派生于Exception的类,或者派生于Exception的某个子类,如IOException。

习惯的做法是,自定义的这个类应该包含两个构造器,一个是默认的构造器,另一个是包含详细描述信息的构造器。

实例:

class FileFormatException extends IOException

{

public FileFormatException() {}

public FileFormatException(String gripe){

super(gripe)

}

}

现在,就可以抛出自己定义的异常类型了:

String readData(BufferedReader in) throws FileFormatException

{

....................

}

4、捕获异常

现在已经知道了如何抛出一个异常,这个过程十分容易,只要将其抛出就不用理睬了。但是,有些代码必须捕获异常。捕获异常需要做更多规划。

要想捕获一个异常,需要设置try/cath语句块。

实例:

try

{

code

more code

more code

}

catch (ExceptionType e)

{

handler for this type //编写处理代码

}

finally

{

code; //不管是否有异常被捕获,finally子句中的代码都会执行

}

如果try语句块中的任何代码抛出了catch子句指定的一个异常类,那么:

程序将跳过try语句块的其余代码;

程序将执行catch子句中的处理器代码;

如果try语句块中的代码没有抛出任何异常,那么程序将跳出catch子句。如果方法中的任何代码抛出了catch子句中没有生命的一个异常类型,那么这个方法就会立即退出。

5、捕获多个异常

在一个try语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理。要为每个异常类型使用一个单独的catch子句。

实例:

try

{

code that might throw exceptions

}

catch (FileNotFoundException e)

{

emergency action for missing files

}

catch (UnkownHostException e) {

emergency action for unkonwn hosts

}

finally

{

code; //不管是否有异常被捕获,finally子句中的代码都会执行

}

相关推荐
扬子鳄0083 分钟前
java注解的处理器
java
Amagi.7 分钟前
Spring中Bean的作用域
java·后端·spring
2402_8575893631 分钟前
Spring Boot新闻推荐系统设计与实现
java·spring boot·后端
繁依Fanyi34 分钟前
旅游心动盲盒:开启个性化旅行新体验
java·服务器·python·算法·eclipse·tomcat·旅游
J老熊39 分钟前
Spring Cloud Netflix Eureka 注册中心讲解和案例示范
java·后端·spring·spring cloud·面试·eureka·系统架构
蜜桃小阿雯41 分钟前
JAVA开源项目 旅游管理系统 计算机毕业设计
java·开发语言·jvm·spring cloud·开源·intellij-idea·旅游
CoderJia程序员甲42 分钟前
重学SpringBoot3-集成Redis(四)之Redisson
java·spring boot·redis·缓存
sco528243 分钟前
SpringBoot 集成 Ehcache 实现本地缓存
java·spring boot·后端
OLDERHARD1 小时前
Java - LeetCode面试经典150题 - 矩阵 (四)
java·leetcode·面试
原机小子1 小时前
在线教育的未来:SpringBoot技术实现
java·spring boot·后端