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子句中的代码都会执行

}

相关推荐
一嘴一个橘子11 小时前
mybatis - 动态语句、批量注册mapper、分页插件
java
组合缺一11 小时前
Json Dom 怎么玩转?
java·json·dom·snack4
危险、11 小时前
一套提升 Spring Boot 项目的高并发、高可用能力的 Cursor 专用提示词
java·spring boot·提示词
kaico201811 小时前
JDK11新特性
java
钊兵11 小时前
java实现GeoJSON地理信息对经纬度点的匹配
java·开发语言
jiayong2311 小时前
Tomcat性能优化面试题
java·性能优化·tomcat
秋刀鱼程序编程11 小时前
Java基础入门(五)----面向对象(上)
java·开发语言
纪莫11 小时前
技术面:MySQL篇(InnoDB的锁机制)
java·数据库·java面试⑧股
Remember_99312 小时前
【LeetCode精选算法】滑动窗口专题二
java·开发语言·数据结构·算法·leetcode
Filotimo_12 小时前
在java开发中,cron表达式概念
java·开发语言·数据库