Java笔记

(1)Java中finally与return的执行顺序

在 Java 的异常处理中,try、catch 和 finally 是按顺序执行的。如果 try 中没有异常,则顺序为 try→finally,如果 try 中有异常,则顺序为 try→catch→finally。但是当 try、catch、finally 中加入 return 之后,return 和 finally 的执行顺序让很多人混淆不清。下面来分别说明一下。

1. try 和 catch 中带有 return

1)try 中带有 return

public class tryDemo {
public static int show() {
try {
return 1;        }
finally {
System.out.println("执行finally模块");
}
}
public static void main(String args[]) {
System.out.println(show());
}
}

输出结果如下:

执行finally模块
1

2)try 和 catch 中都带有 return

public class tryDemo {
public static int show() {
try {
int a = 8 / 0;
return 1;        }
catch (Exception e) {
return 2;        }
finally {
System.out.println("执行finally模块");        }
}
public static void main(String args[]) {
System.out.println(show());
}
}
输出结果为:
执行finally模块
2

当 try 代码块或者 catch 代码块中有 return 时,finally 中的代码总会被执行,且 finally 语句 return 返回之前执行。

注意:可以使用编译器的 Debug 功能查看详细过程。如果不了解如何使用 Debug 功能可参考《Java Eclipse如何调试代码》一节。

2. finally 中带有 return

public class tryDemo {
public static int show() {
try {
int a = 8 / 0;
return 1;        }
catch (Exception e)
{
return 2;
}
finally {
System.out.println("执行finally模块");
return 0;
}
}
public static void main(String args[]) {
System.out.println(show());
}
}
输出结果如下:执行finally模块
0

当 finally 有返回值时,会直接返回该值,不会去返回 try 代码块或者 catch 代码块中的返回值。

注意:finally 代码块中最好不要包含 return 语句,否则程序会提前退出。

3. finally 中改变返回值

下面先来看 try 代码块或者 catch 代码块中的返回值是普通变量时,代码如下:

public class tryDemo {
public static int show() {
int result = 0;
try {
return result;        }
finally {
System.out.println("执行finally模块");
result = 1;        }
}
public static void main(String args[]) {
System.out.println(show());    }
}
输出结果为:
执行finally模块
0

由输出结果可以看出,在 finally 代码块中改变返回值并不会改变最后返回的内容。

当返回值类型是引用类型时,结果也是一样的,代码如下:

public class tryDemo {
public static Object show() {
Object obj = new Object();
try {
return obj;        }
finally {
System.out.println("执行finally模块");
obj = null;
}
}
public static void main(String args[]) {
System.out.println(show());    }}
输出结果为:
执行finally模块
java.lang.Object@15db9742

当 try 代码块或 catch 代码块中的 return 返回值类型为普通变量或引用变量时,即使在后面 finally 代码块中对返回值的变量重新赋值,也不会影响最后返回的值。

总结为以下几条:

  • 当 try 代码块和 catch 代码块中有 return 语句时,finally 仍然会被执行。
  • 执行 try 代码块或 catch 代码块中的 return 语句之前,都会先执行 finally 语句。
  • 无论在 finally 代码块中是否修改返回值,返回值都不会改变,仍然是执行 finally 代码块之前的值。
  • finally 代码块中的 return 语句一定会执行。

(2)为什么使用向上转型而不直接创建子类对象?

初学者在学习向上转型可能会很难理解,向上转型并不能调用子类特有属性和方法,我们必须先生成子类实例再赋值给父类引用(向上转型),然后将父类引用向下强制转换给子类引用(向下转型),这样才能调用子类中的所有成员。这看起来像是多次一举,还不如直接创建子类实例。

随着技术的提升,我们在学习其它开源项目时会发现很多地方都用了向上转型和向下转型的技术。本节将带大家了解向上转型和向下转型的意义及使用场景。

例 1

定义父类 Animal,代码如下:

public class Animal {
public void sleep() {
System.out.println("小动物在睡觉");    }
public static void doSleep(Animal animal) {
// 此时的参数是父类对象,但是实际调用时传递的是子类对象,就是向上转型。
//为何doSleep(Animal animal),为何参数是Animal,因为想用向上转型(Animal animal=new Cat();)
//向下转型:Cat cat=(Cat)new Animal();
//如果不用向上转型,那就得写两个doSleep办法,一个参数为Cat cat 一个参数为Dog dog,那代码就重复了
//向上转型更好的体现了类的多态性animal.sleep();    }
public static void main(String[] args) {
Animal animal1=new Cat();//向上转型
Animal animal2=new Dog();//向上转型
animal.doSleep(animal1);
animal.doSleep(animal2);
}
}

子类 Cat 代码如下:

public class Cat extends Animal {
@Override
public void sleep() {
System.out.println("猫正在睡觉");    }}

子类 Dog 代码如下:

public class Dog extends Animal {
@Override    public void sleep() {
System.out.println("狗正在睡觉");
}}
输出结果为:
猫正在睡觉
狗正在睡觉

如果不用向上转型则必须写两个 doSleep 方法,一个传递 Cat 类对象,一个传递 Dog 类对象。这还是两个子类,如果有多个子类就要写很多相同的方法,造成重复。可以看出向上转型更好的体现了类的多态性,增强了程序的间接性以及提高了代码的可扩展性。当需要用到子类特有的方法时可以向下转型,这也就是为什么要向下转型。

比如设计一个父类 FileRead 用来读取文件,ExcelRead 类和 WordRead 类继承 FileRead 类。在使用程序的时候,往往事先不知道我们要读入的是 Excel 还是 Word。所以我们向上转型用父类去接收,然后在父类中实现自动绑定,这样无论你传进来的是 Excel 还是 Word 就都能够完成文件读取。

总结如下:

  1. 把子类对象直接赋给父类引用是向上转型,向上转型自动转换。如 Father father = new Son();
  2. 指向子类对象的父类引用赋给子类引用是向下转型,要强制转换。使用向下转型,必须先向上转型,为了安全可以用 instanceof 运算符判断。 如 father 是一个指向子类对象的父类引用,把 father 赋给子类引用 son,即Son son =(Son)father;。其中 father 前面的(Son)必须添加,进行强制转换。
  3. 向上转型不能使用子类特有的属性和方法,只能引用父类的属性和方法,但是子类重写父类的方法是有效的。
  4. 向上转型时会优先使用子类中重写父类的方法,如例 1 中调用的 sleep 方法。
  5. 向上转型的作用是减少重复代码,可以将父类作为参数,这样使代码变得简洁,也更好的体现了多态。

(3)Java抽象类和接口的区别

1)抽象类

在 Java 中,被关键字 abstract 修饰的类称为抽象类;被 abstract 修饰的方法称为抽象方法,抽象方法只有方法声明没有方法体。

抽象类有以下几个特点:

  1. 抽象类不能被实例化,只能被继承。
  2. 包含抽象方法的类一定是抽象类,但抽象类不一定包含抽象方法(抽象类可以包含普通方法)。
  3. 抽象方法的权限修饰符只能为 public、protected 或 default,默认情况下为 public。
  4. 一个类继承于一个抽象类,则子类必须实现抽象类的抽象方法,如果子类没有实现父类的抽象方法,那子类必须定义为抽象类。
  5. 抽象类可以包含属性、方法、构造方法,但构造方法不能用来实例化对象,只能被子类调用。

2)接口

接口可以看成是一种特殊的类,只能用 interface 关键字修饰。

Java 中的接口具有以下几个特点:

  1. 接口中可以包含变量和方法,变量被隐式指定为 public static final,方法被隐式指定为 public abstract(JDK 1.8 之前)。
  2. 接口支持多继承,即一个接口可以继承(extends)多个接口,间接解决了 Java 中类不能多继承的问题。
  3. 一个类可以同时实现多个接口,一个类实现某个接口则必须实现该接口中的抽象方法,否则该类必须被定义为抽象类。

3)抽象类和接口的区别

接口和抽象类很像,它们都具有如下特征。

  • 接口和抽象类都不能被实例化,主要用于被其他类实现和继承。
  • 接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。

但接口和抽象类之间的差别非常大,这种差别主要体现在二者设计目的上。下面具体分析二者的差别。

接口作为系统与外界交互的窗口,接口体现的是一种规范。对于接口的实现者而言,接口规定了实现者必须向外提供哪些服务(以方法的形式来提供);对于接口的调用者而言,接口规定了调用者可以调用哪些服务,以及如何调用这些服务(就是如何来调用方法)。当在一个程序中使用接口时,接口是多个模块间的耦合标准;当在多个应用程序之间使用接口时,接口是多个程序之间的通信标准。

从某种程度上来看,接口类似于整个系统的“总纲”,它制定了系统各模块应该遵循的标准,因此一个系统中的接口不应该经常改变。一旦接口被改变,对整个系统甚至其他系统的影响将是辐射式的,会导致系统中大部分类都需要改写。

抽象类则不一样,抽象类作为系统中多个子类的共同父类,它所体现的是一种模板式设计。抽象类作为多个子类的抽象父类,可以被当成系统实现过程中的中间产品,这个中间产品已经实现了系统的部分功能(那些已经提供实现的方法),但这个产品依然不能当成最终产品,必须有更进一步的完善,这种完善可能有几种不同方式。

除此之外,接口和抽象类在用法上也存在差别,如下表所示:

参数 抽象类 接口
实现 子类使用 extends 关键字来继承抽象类,如果子类不是抽象类,则需要提供抽象类中所有声明的方法的实现。 子类使用 implements 关键字来实现接口,需要提供接口中所有声明的方法的实现。
访问修饰符 可以用 public、protected 和 default 修饰 默认修饰符是 public,不能使用其它修饰符
方法 完全可以包含普通方法 只能包含抽象方法、静态方法、默认方法和私有方法,不能为普通方法提供方法实现
变量 既可以定义普通成员变量,也可以定义静态常量 只能定义静态常量,不能定义普通成员变量
构造方法 抽象类里的构造方法并不是用于创建对象,而是让其子类调用这些构造方法来完成属于抽象类的初始化操作 没有构造方法
初始化块 可以包含初始化块 不能包含初始化块
main 方法 可以有 main 方法,并且能运行 没有 main 方法
与普通Java类的区别 抽象类不能实例化,除此之外和普通 Java 类没有任何区别 是完全不同的类型
运行速度 比接口运行速度要快 需要时间去寻找在类种实现的方法,所以运行速度稍微有点慢

一个类最多只能有一个直接父类,包括抽象类,但一个类可以直接实现多个接口,通过实现多个接口可以弥补 Java 单继承的不足。

4)抽象类和接口的应用场景

抽象类的应用场景:

  1. 父类只知道其子类应该包含怎样的方法,不能准确知道这些子类如何实现这些方法的情况下,使用抽象类。
  2. 从多个具有相同特征的类中抽象出一个抽象类,以这个抽象类作为子类的模板,从而避免了子类设计的随意性。

接口的应用场景:

  1. 一般情况下,实现类和它的抽象类之前具有 “is-a” 的关系,但是如果我们想达到同样的目的,但是又不存在这种关系时,使用接口。
  2. 由于 Java 中单继承的特性,导致一个类只能继承一个类,但是可以实现一个或多个接口,此时可以使用接口。

什么时候使用抽象类和接口:

  • 如果拥有一些方法并且想让它们有默认实现,则使用抽象类。
  • 如果想实现多重继承,那么必须使用接口。因为 Java 不支持多继承,子类不能继承多个类,但可以实现多个接口,因此可以使用接口。
  • 如果基本功能在不断改变,那么就需要使用抽象类。如果使用接口并不断需要改变基本功能,那么就需要改变所有实现了该接口的类。

(4)Java File类(文件操作类)详解

在 Java 中,File 类是 java.io 包中唯一代表磁盘文件本身的对象,也就是说,如果希望在程序中操作文件和目录,则都可以通过 File 类来完成。File 类定义了一些方法来操作文件,如新建、删除、重命名文件和目录等。

File 类不能访问文件内容本身,如果需要访问文件内容本身,则需要使用输入/输出流。

File 类提供了如下三种形式构造方法。

  1. File(String path):如果 path 是实际存在的路径,则该 File 对象表示的是目录;如果 path 是文件名,则该 File 对象表示的是文件。
  2. File(String path, String name):path 是路径名,name 是文件名。
  3. File(File dir, String name):dir 是路径对象,name 是文件名。

使用任意一个构造方法都可以创建一个 File 对象,然后调用其提供的方法对文件进行操作。在表 1 中列出了 File 类的常用方法及说明。

方法名称 说明
boolean canRead() 测试应用程序是否能从指定的文件中进行读取
boolean canWrite() 测试应用程序是否能写当前文件
boolean delete() 删除当前对象指定的文件
boolean exists() 测试当前 File 是否存在
String getAbsolutePath() 返回由该对象表示的文件的绝对路径名
String getName() 返回表示当前对象的文件名或路径名(如果是路径,则返回最后一级子路径名)
String getParent() 返回当前 File 对象所对应目录(最后一级子目录)的父目录名
boolean isAbsolute() 测试当前 File 对象表示的文件是否为一个绝对路径名。该方法消除了不同平台的差异,可以直接判断 file 对象是否为绝对路径。在 UNIX/Linux/BSD 等系统上,如果路径名开头是一条斜线/,则表明该 File 对象对应一个绝对路径;在 Windows 等系统上,如果路径开头是盘符,则说明它是一个绝对路径。
boolean isDirectory() 测试当前 File 对象表示的文件是否为一个路径
boolean isFile() 测试当前 File 对象表示的文件是否为一个“普通”文件
long lastModified() 返回当前 File 对象表示的文件最后修改的时间
long length() 返回当前 File 对象表示的文件长度
String[] list() 返回当前 File 对象指定的路径文件列表
String[] list(FilenameFilter) 返回当前 File 对象指定的目录中满足指定过滤器的文件列表
boolean mkdir() 创建一个目录,它的路径名由当前 File 对象指定
boolean mkdirs() 创建一个目录,它的路径名由当前 File 对象指定
boolean renameTo(File) 将当前 File 对象指定的文件更名为给定参数 File 指定的路径名

File 类中有以下两个常用常量:

  • public static final String pathSeparator:指的是分隔连续多个路径字符串的分隔符,Windows 下指;。例如 java -cp test.jar;abc.jar HelloWorld
  • public static final String separator:用来分隔同一个路径字符串中的目录的,Windows 下指/。例如 C:/Program Files/Common Files

注意:可以看到 File 类的常量定义的命名规则不符合标准命名规则,常量名没有全部大写,这是因为 Java 的发展经过了一段相当长的时间,而命名规范也是逐步形成的,File 类出现较早,所以当时并没有对命名规范有严格的要求,这些都属于 Java 的历史遗留问题。

Windows 的路径分隔符使用反斜线“\”,而 Java 程序中的反斜线表示转义字符,所以如果需要在 Windows 的路径下包括反斜线,则应该使用两条反斜线或直接使用斜线“/”也可以。Java 程序支持将斜线当成平台无关的路径分隔符。

假设在 Windows 操作系统中有一文件 D:\javaspace\hello.java,在 Java 中使用的时候,其路径的写法应该为 D:/javaspace/hello.java 或者 D:\\javaspace\\hello.java

1)获取文件属性

在 Java 中获取文件属性信息的第一步是先创建一个 File 类对象并指向一个已存在的文件, 然后调用表 1 中的方法进行操作。

假设有一个文件位于 C:\windows\notepad.exe。编写 Java 程序获取并显示该文件的长度、是否可写、最后修改日期以及文件路径等属性信息。实现代码如下:

public class Test02 {
public static void main(String[] args) {
String path = "C:/windows/"; // 指定文件所在的目录
File f = new File(path, "notepad.exe"); // 建立File变量,并设定由f变量引用        System.out.println("C:\\windows\\notepad.exe文件信息如下:");        System.out.println("============================================");
System.out.println("文件长度:" + f.length() + "字节");
System.out.println("文件或者目录:" + (f.isFile() ? "是文件" : "不是文件"));
System.out.println("文件或者目录:" + (f.isDirectory() ? "是目录" : "不是目录"));
System.out.println("是否可读:" + (f.canRead() ? "可读取" : "不可读取"));
System.out.println("是否可写:" + (f.canWrite() ? "可写入" : "不可写入"));
System.out.println("是否隐藏:" + (f.isHidden() ? "是隐藏文件" : "不是隐藏文件"));
System.out.println("最后修改日期:" + new Date(f.lastModified()));
System.out.println("文件名称:" + f.getName());
System.out.println("文件路径:" + f.getPath());
System.out.println("绝对路径:" + f.getAbsolutePath());    }}

在上述代码中 File 类构造方法的第一个参数指定文件所在位置,这里使用C:/作为文件的实际路径;第二个参数指定文件名称。创建的 File 类对象为 f,然后通过 f 调用方法获取相应的属性,最终运行效果如下所示。

C:\windows\notepad.exe文件信息如下:
============================================
文件长度:193536字节
文件或者目录:是文件
文件或者目录:不是目录
是否可读:可读取
是否可写:可写入
是否隐藏:不是隐藏文件
最后修改日期:Mon Dec 28 02:55:19 CST 2016
文件名称:notepad.exe
文件路径:C:\windows\notepad.exe
绝对路径:C:\windows\notepad.exe

2)创建和删除文件

File 类不仅可以获取已知文件的属性信息,还可以在指定路径创建文件,以及删除一个文件。创建文件需要调用 createNewFile() 方法,删除文件需要调用 delete() 方法。无论是创建还是删除文件通常都先调用 exists() 方法判断文件是否存在。

假设要在 C 盘上创建一个 test.txt 文件,程序启动时会检测该文件是否存在,如果不存在则创建;如果存在则删除它再创建。

实现代码如下:

public class Test03 {    public static void main(String[] args) throws IOException {        File f = new File("C:\\test.txt"); // 创建指向文件的File对象        if (f.exists()) // 判断文件是否存在        {            f.delete(); // 存在则先删除        }        f.createNewFile(); // 再创建    }}

运行程序之后可以发现,在 C 盘中已经创建好了 test.txt 文件。但是如果在不同的操作系统中,路径的分隔符是不一样的,例如:

  • Windows 中使用反斜杠\表示目录的分隔符。
  • Linux 中使用正斜杠/表示目录的分隔符。

那么既然 Java 程序本身具有可移植性的特点,则在编写路径时最好可以根据程序所在的操作系统自动使用符合本地操作系统要求的分隔符,这样才能达到可移植性的目的。要实现这样的功能,则就需要使用 File 类中提供的两个常量。

代码修改如下:

public static void main(String[] args) throws IOException {
String path = "C:" + File.separator + "test.txt"; // 拼凑出可以适应操作系统的路径
File f = new File(path);
if (f.exists()) // 判断文件是否存在    {
f.delete(); // 存在则先删除    }
f.createNewFile(); // 再创建}

程序的运行结果和前面程序一样,但是此时的程序可以在任意的操作系统中使用。

注意:在操作文件时一定要使用 File.separator 表示分隔符。在程序的开发中,往往会使用 Windows 开发环境,因为在 Windows 操作系统中支持的开发工具较多,使用方便,而在程序发布时往往是直接在 Linux 或其它操作系统上部署,所以这时如果不使用 File.separator,则程序运行就有可能存在问题。关于这一点我们在以后的开发中一定要有所警惕。

2)创建和删除目录

File 类除了对文件的创建和删除外,还可以创建和删除目录。创建目录需要调用 mkdir() 方法,删除目录需要调用 delete() 方法。无论是创建还是删除目录都可以调用 exists() 方法判断目录是否存在。

编写一个程序判断 C 盘根目录下是否存在 config 目录,如果存在则先删除再创建。实现代码如下:

public class Test04 {
public static void main(String[] args) {
String path = "C:/config/"; // 指定目录位置
File f = new File(path); // 创建File对象
if (f.exists()) {
f.delete();        }
f.mkdir(); // 创建目录    }}

3)遍历目录

通过遍历目录可以在指定的目录中查找文件,或者显示所有的文件列表。File 类的 list() 方法提供了遍历目录功能,该方法有如下两种重载形式。

1. String[] list()

该方法表示返回由 File 对象表示目录中所有文件和子目录名称组成的字符串数组,如果调用的 File 对象不是目录,则返回 null。

提示:list() 方法返回的数组中仅包含文件名称,而不包含路径。但不保证所得数组中的相同字符串将以特定顺序出现,特别是不保证它们按字母顺序出现。

2. String[] list(FilenameFilter filter)

该方法的作用与 list() 方法相同,不同的是返回数组中仅包含符合 filter 过滤器的文件和目录,如果 filter 为 null,则接受所有名称。

假设要遍历 C 盘根目录下的所有文件和目录,并显示文件或目录名称、类型及大小。使用 list() 方法的实现代码如下:

public class Test05 {
public static void main(String[] args) {
File f = new File("C:/"); // 建立File变量,并设定由f变量变数引用
System.out.println("文件名称\t\t文件类型\t\t文件大小");        System.out.println("===================================================");
String fileList[] = f.list(); // 调用不带参数的list()方法
for (int i = 0; i < fileList.length; i++) { // 遍历返回的字符数组
System.out.print(fileList[i] + "\t\t");
System.out.print((new File("C:/", fileList[i])).isFile() ? "文件" + "\t\t" : "文件夹" + "\t\t");            System.out.println((new File("C:/", fileList[i])).length() + "字节");        }    }}

由于 list() 方法返回的字符数组中仅包含文件名称,因此为了获取文件类型和大小,必须先转换为 File 对象再调用其方法。如下所示的是实例的运行效果:

文件名称  文件类型  文件大小
===================================================
$Recycle.Bin  文件夹  4096字节
Documents and Settings  文件夹  0字节
Download  文件夹  0字节
DRIVERS  文件夹  0字节
FibocomLog  文件夹  0字节
Gateface  文件夹  0字节
GFPageCache  文件夹  0字节
hiberfil.sys  文件  3375026176字节
Intel  文件夹  0字节
KuGou  文件夹  0字节
logs  文件夹  0字节
msdia80.dll  文件  904704字节
MSOCache  文件夹  0字节
MyDownloads  文件夹  0字节
MyDrivers  文件夹  0字节
news.template  文件  417字节
NVIDIA  文件夹  0字节
OneDriveTemp  文件夹  0字节
opt  文件夹  0字节
pagefile.sys  文件  6442450944字节
PerfLogs  文件夹  0字节
Program Files  文件夹  12288字节
Program Files (x86)  文件夹  8192字节
ProgramData  文件夹  12288字节
QMDownload  文件夹  0字节
Recovery  文件夹  0字节
swapfile.sys  文件  268435456字节
System Volume Information  文件夹  12288字节
Users  文件夹  4096字节
Windows  文件夹  16384字节

假设希望只列出目录下的某些文件,这就需要调用带过滤器参数的 list() 方法。首先需要创建文件过滤器,该过滤器必须实现 java.io.FilenameFilter 接口,并在 accept() 方法中指定允许的文件类型。

如下所示为允许 SYS、TXT 和 BAK 格式文件的过滤器实现代码:

public class ImageFilter implements FilenameFilter {    // 实现 FilenameFilter 接口    @Override    public boolean accept(File dir, String name) {        // 指定允许的文件类型        return name.endsWith(".sys") || name.endsWith(".txt") || name.endsWith(".bak");    }}

上述代码创建的过滤器名称为 ImageFilter,接下来只需要将该名称传递给 list() 方法即可实现筛选文件。如下所示为修改后的 list() 方法,其他代码与例 4 相同,这里不再重复。

String fileList[] = f.list(new ImageFilter());

再次运行程序,遍历结果如下所示:

文件名称        文件类型        文件大小
===================================================
offline_FtnInfo.txt        文件        296字节
pagefile.sys        文件        8436592640字节

(5)Java Collections类:sort()升序排序、reverse()降序排序、copy()复制、fill()填充

Collections 类提供了许多操作集合的静态方法,借助这些静态方法可以实现集合元素的排序、填充和复制等操作。下面介绍 Collections 类中操作集合的常用方法。

1)正向排序

使用 Collections 类的静态方法 sort() 可以对集合中的元素进行升序排序。这要求列表中的所有元素都必须实现 Comparable 接口,而且所有元素都必须是使用指定比较器可相互比较的。

sort() 方法主要有如下两种重载形式。

  1. void sort(List list):根据元素的自然顺序对集合中的元素进行升序排序。
  2. void sort(List list,Comparator comparator):按 comparator 参数指定的排序方式对集合中的元素进行排序。

编写一个程序,对用户输入的 5 个商品价格进行排序后输出。这里要求使用 Collections 类中 sort() 方法按从低到局的顺序对其进行排序,最后将排序后的成绩输出。

具体实现代码如下:

public class Test10 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
List prices = new ArrayList();
for (int i = 0; i < 5; i++) {
System.out.println("请输入第 " + (i + 1) + " 个商品的价格:");
int p = input.nextInt();
prices.add(Integer.valueOf(p)); // 将录入的价格保存到List集合中        }
Collections.sort(prices); // 调用sort()方法对集合进行排序
System.out.println("价格从低到高的排列为:");
for (int i = 0; i < prices.size(); i++) {
System.out.print(prices.get(i) + "\t");        }    }}

如上述代码,循环录入 5 个价格,并将每个价格都存储到已定义好的 List 集合 prices 中,然后使用 Collections 类的 sort() 方法对该集合元素进行升序排序。最后使用 for 循环遍历 users 集合,输出该集合中的元素。

该程序的执行结果如下所示。

请输入第 1 个商品的价格:
85
请输入第 2 个商品的价格:
48
请输入第 3 个商品的价格:
66
请输入第 4 个商品的价格:
80
请输入第 5 个商品的价格:
18
价格从低到高的排列为:
18    48    66    80    85

2)逆向排序

与 sort() 方法的作用相反,调用 reverse() 静态方法可以对指定集合元素进行逆向排序。该方法的定义如下:

void reverse(List list)    // 对集合中的元素进行反转排序

循环录入 5 个商品的名称,并按录入时间的先后顺序进行降序排序,即后录入的先输出。

下面编写程序,使用 Collections 类的 reverse() 方法对保存到 List 集合中的 5 个商品名称进行反转排序,并输出排序后的商品信息。具体的实现代码如下:

public class Test11 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
List students = new ArrayList();
System.out.println("******** 商品信息 ********");
for (int i = 0; i < 5; i++) {
System.out.println("请输入第 " + (i + 1) + " 个商品的名称:");
String name = input.next();
students.add(name); // 将录入的商品名称存到List集合中        }
Collections.reverse(students); // 调用reverse()方法对集合元素进行反转排序
System.out.println("按录入时间的先后顺序进行降序排列为:");
for (int i = 0; i < 5; i++) {
System.out.print(students.get(i) + "\t");        }    }}

如上述代码,首先循环录入 5 个商品的名称,并将这些名称保存到 List 集合中,然后调用 Collections 类中的 reverse() 方法对该集合元素进行反转排序。最后使用 for 循环将排序后的集合元素输出。

执行该程序,输出结果如下所示。

******** 商品信息 ********
请输入第 1 个商品的名称:
果粒橙
请输入第 2 个商品的名称:
冰红茶
请输入第 3 个商品的名称:
矿泉水
请输入第 4 个商品的名称:
软面包
请输入第 5 个商品的名称:
巧克力
按录入时间的先后顺序进行降序排列为:
巧克力    软面包    矿泉水    冰红茶    果粒橙

3)复制

Collections 类的 copy() 静态方法用于将指定集合中的所有元素复制到另一个集合中。执行 copy() 方法后,目标集合中每个已复制元素的索引将等同于源集合中该元素的索引。

copy() 方法的语法格式如下:

void copy(List <? super T> dest,List<? extends T> src)

其中,dest 表示目标集合对象,src 表示源集合对象。

注意:目标集合的长度至少和源集合的长度相同,如果目标集合的长度更长,则不影响目标集合中的其余元素。如果目标集合长度不够而无法包含整个源集合元素,程序将抛出 IndexOutOfBoundsException 异常。

在一个集合中保存了 5 个商品名称,现在要使用 Collections 类中的 copy() 方法将其中的 3 个替换掉。具体实现的代码如下:

public class Test12 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
List srcList = new ArrayList();
List destList = new ArrayList();
destList.add("苏打水");
destList.add("木糖醇");
destList.add("方便面");
destList.add("火腿肠");
destList.add("冰红茶");
System.out.println("原有商品如下:");
for (int i = 0; i < destList.size(); i++) {
System.out.println(destList.get(i));        }
System.out.println("输入替换的商品名称:");
for (int i = 0; i < 3; i++) {
System.out.println("第 " + (i + 1) + " 个商品:");
String name = input.next();
srcList.add(name);        }
// 调用copy()方法将当前商品信息复制到原有商品信息集合中
Collections.copy(destList, srcList);
System.out.println("当前商品有:");
for (int i = 0; i < destList.size(); i++) {
System.out.print(destList.get(i) + "\t");        }    }}

如上述代码,首先创建了两个 List 对象 srcList 和 destList,并向 destList 集合中添加了 5 个元素,向 srcList 集合中添加了 3 个元素,然后调用 Collections 类中 copy() 方法将 srcList 集合中的全部元素复制到 destList 集合中。由于 destList 集合中含有 5 个元素,故最后两个元素不会被覆盖。

运行该程序,具体的执行结果如下所示。

原有商品如下:
苏打水
木糖醇
方便面
火腿肠
冰红茶
输入替换的商品名称:
第 1 个商品:
燕麦片
第 2 个商品:
八宝粥
第 3 个商品:
软面包
当前商品有:
燕麦片    八宝粥    软面包    火腿肠    冰红茶

4)填充

Collections 类的 fill() 静态方法可以对指定集合的所有元素进行填充操作。fill() 方法的定义如下:

void fill(List<? super T> list,T obj)    // 使用指定元素替换指定列表中的所有元素

其中,list 表示要替换的集合对象,obj 表示用来替换指定集合的元素值。

编写一个程序,要求用户输入 3 个商品名称,然后使用 Collections 类中的 fill() 方法对商品信息进行重置操作,即将所有名称都更改为“未填写”。具体的实现代码如下:

public class Test13 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
List products = new ArrayList();
System.out.println("******** 商品信息 ********");
for (int i = 0; i < 3; i++) {
System.out.println("请输入第 " + (i + 1) + " 个商品的名称:");
String name = input.next();
products.add(name); // 将用户录入的商品名称保存到List集合中        }
System.out.println("重置商品信息,将所有名称都更改为'未填写'");
Collections.fill(products, "未填写");
System.out.println("重置后的商品信息为:");
for (int i = 0; i < products.size(); i++) {
System.out.print(products.get(i) + "\t");
}    }}

如上述代码,首先循环录入 3 个商品名称,并将这些商品信息存储到 List 集合中,然后调用 Collections 类中的 fill() 方法将该集合中的所有元素值替换为“未填写”。最后使用 for 循环将替换后的集合元素输出。

运行该程序,执行结果如下所示。

******** 商品信息 ********
请输入第 1 个商品的名称:
苏打水
请输入第 2 个商品的名称:
矿泉水
请输入第 3 个商品的名称:
冰红茶
重置商品信息,将所有名称都更改为'未填写'
重置后的商品信息为:
未填写    未填写    未填写

(6)Java Map集合详解

Map 是一种键-值对(key-value)集合,Map 集合中的每一个元素都包含一个键对象和一个值对象。其中,键对象不允许重复,而值对象可以重复,并且值对象还可以是 Map 类型的,就像数组中的元素还可以是数组一样。

Map 接口主要有两个实现类:HashMap 类和 TreeMap 类。其中,HashMap 类按哈希算法来存取键对象,而 TreeMap 类可以对键对象进行排序。

Map 接口中提供的常用方法如表 1 所示。

方法名称 说明
V get(Object key) 返回 Map 集合中指定键对象所对应的值。V 表示值的数据类型
V put(K key, V value) 向 Map 集合中添加键-值对,返回 key 以前对应的 value,如果没有, 则返回 null
V remove(Object key) 从 Map 集合中删除 key 对应的键-值对,返回 key 对应的 value,如果没有,则返回null
Set entrySet() 返回 Map 集合中所有键-值对的 Set 集合,此 Set 集合中元素的数据类型为 Map.Entry
Set keySet() 返回 Map 集合中所有键对象的 Set 集合

例 1

每名学生都有属于自己的唯一编号,即学号。在毕业时需要将该学生的信息从系统中移除。

下面编写 Java 程序,使用 HashMap 来存储学生信息,其键为学生学号,值为姓名。毕业时,需要用户输入学生的学号,并根据学号进行删除操作。具体的实现代码如下:

public class Test09 {
public static void main(String[] args) {
HashMap users = new HashMap();
users.put("11", "张浩太"); // 将学生信息键值对存储到Map中
users.put("22", "刘思诚");
users.put("33", "王强文");
users.put("44", "李国量");
users.put("55", "王路路");
System.out.println("******** 学生列表 ********");
Iterator it = users.keySet().iterator();
while (it.hasNext()) {            // 遍历 Map
Object key = it.next();
Object val = users.get(key);
System.out.println("学号:" + key + ",姓名:" + val);        }
Scanner input = new Scanner(System.in);
System.out.println("请输入要删除的学号:");
int num = input.nextInt();
if (users.containsKey(String.valueOf(num))) { // 判断是否包含指定键            users.remove(String.valueOf(num)); // 如果包含就删除        }
else {            System.out.println("该学生不存在!");        }
System.out.println("******** 学生列表 ********");
it = users.keySet().iterator();
while (it.hasNext()) {
Object key = it.next();
Object val = users.get(key);
System.out.println("学号:" + key + ",姓名:" + val);        }    }}

在该程序中,两次使用 while 循环遍历 HashMap 集合。当有学生毕业时,用户需要输入该学生的学号,根据学号使用 HashMap 类的 remove() 方法将对应的元素删除。程序运行结果如下所示。

******** 学生列表 ********
学号:44,姓名:李国量
学号:55,姓名:王路路
学号:22,姓名:刘思诚
学号:33,姓名:王强文
学号:11,姓名:张浩太
请输入要删除的学号:
22
******** 学生列表 ********
学号:44,姓名:李国量
学号:55,姓名:王路路
学号:33,姓名:王强文
学号:11,姓名:张浩太
******** 学生列表 ********
学号:44,姓名:李国量
学号:55,姓名:王路路
学号:22,姓名:刘思诚
学号:33,姓名:王强文
学号:11,姓名:张浩太
请输入要删除的学号:
44
******** 学生列表 ********
学号:55,姓名:王路路
学号:22,姓名:刘思诚
学号:33,姓名:王强文
学号:11,姓名:张浩太

注意:TreeMap 类的使用方法与 HashMap 类相同,唯一不同的是 TreeMap 类可以对键对象进行排序,这里不再赘述。

(7)Java Set集合详解:HashSet类、TreeSet 类

Set 集合也实现了 Collection 接口,它主要有两个实现类:HashSet 类和 TreeSet类。Set 集合中的对象不按特定的方式排序,只是简单地把对象加入集合,集合中不能包含重复的对象,并且最多只允许包含一个 null 元素。

HashSet 类

HashSet 类是按照哈希算法来存储集合中的元素,使用哈希算法可以提高集合元素的存储速度,当向 Set 集合中添加一个元素时,HashSet 会调用该元素的 hashCode() 方法,获取其哈希码,然后根据这个哈希码计算出该元素在集合中的存储位置。

在 HashSet 类中实现了 Collection 接口中的所有方法。HashSet 类的常用构造方法重载形式如下。

  • HashSet():构造一个新的空的 Set 集合。
  • HashSet(Collection<? extends E>c):构造一个包含指定 Collection 集合元素的新 Set 集合。其中,“< >”中的 extends 表示 HashSet 的父类,即指明该 Set 集合中存放的集合元素类型。c 表示其中的元素将被存放在此 Set 集合中。

下面的代码演示了创建两种不同形式的 HashSet 对象。

HashSet hs = new HashSet();    // 调用无参的构造函数创建HashSet对象HashSet<String> hss = new HashSet<String>();    // 创建泛型的 HashSet 集合对象

例 1

编写一个 Java 程序,使用 HashSet 创建一个 Set 集合,并向该集合中添加 5 本图书名称。具体实现代码如下:

public static void main(String[] args) {
HashSet<String> bookSet = new HashSet<String>(); // 创建一个空的 Set 集合    String book1 = new String("如何成为 Java 编程高手");
String book2 = new String("Java 程序设计一百例");
String book3 = new String("从零学 Java 语言");
String book4 = new String("论 java 的快速开发");
bookSet.add(book1); // 将 book1 存储到 Set 集合中
bookSet.add(book2); // 将 book2 存储到 Set 集合中
bookSet.add(book3); // 将 book3 存储到 Set 集合中
bookSet.add(book4); // 将 book4 存储到 Set 集合中
System.out.println("新进图书有:");
Iterator<String> it = bookSet.iterator();
while (it.hasNext()) {
System.out.println("《" + (String) it.next() + "》"); // 输出 Set 集合中的元素    }
System.out.println("共采购 " + bookSet.size() + " 本图书!");}

如上述代码,首先使用 HashSet 类的构造方法创建了一个 Set 集合,接着创建了 4 个 String 类型的对象,并将这些对象存储到 Set 集合中。使用 HashSet 类中的 iterator() 方法获取一个 Iterator 对象,并调用其 hasNext() 方法遍历集合元素,再将使用 next() 方法读取的元素强制转换为 String 类型。最后调用 HashSet 类中的 size() 方法获取集合元素个数。

运行该程序,输出的结果如下:

新进图书有:
《如何成为 Java 编程高手》
《从零学 Java 语言》
《Java 程序设计一百例》
《论 java 的快速开发》
共采购 4 本图书!

注意:在该示例中,如果再向 bookSet 集合中添加一个名称为“Java 程序设计一百例”的 String 对象,则输出的结果与上述执行结果相同。也就是说,如果向 Set 集合中添加两个相同的元素,则后添加的会覆盖前面添加的元素,即在 Set 集合中不会出现相同的元素。

TreeSet 类

TreeSet 类同时实现了 Set 接口和 SortedSet 接口。SortedSet 接口是 Set 接口的子接口,可以实现对集合进行自然排序,因此使用 TreeSet 类实现的 Set 接口默认情况下是自然排序的,这里的自然排序指的是升序排序。

TreeSet 只能对实现了 Comparable 接口的类对象进行排序,因为 Comparable 接口中有一个 compareTo(Object o) 方法用于比较两个对象的大小。例如 a.compareTo(b),如果 a 和 b 相等,则该方法返回 0;如果 a 大于 b,则该方法返回大于 0 的值;如果 a 小于 b,则该方法返回小于 0 的值。

表 1 列举了 JDK 类库中实现 Comparable 接口的类,以及这些类对象的比较方式。

比较方式
包装类(BigDecimal、Biglnteger、 Byte、Double、 Float、Integer、Long 及 Short) 按数字大小比较
Character 按字符的 Unicode 值的数字大小比较
String 按字符串中字符的 Unicode 值的数字大小比较

TreeSet 类除了实现 Collection 接口的所有方法之外,还提供了如表 2 所示的方法。

方法名称 说明
E first() 返回此集合中的第一个元素。其中,E 表示集合中元素的数据类型
E last() 返回此集合中的最后一个元素
E poolFirst() 获取并移除此集合中的第一个元素
E poolLast() 获取并移除此集合中的最后一个元素
SortedSet subSet(E fromElement,E toElement) 返回一个新的集合,新集合包含原集合中 fromElement 对象与 toElement 对象之间的所有对象。包含 fromElement 对象,不包含 toElement 对象
SortedSet headSet<E toElement〉 返回一个新的集合,新集合包含原集合中 toElement 对象之前的所有对象。 不包含 toElement 对象
SortedSet tailSet(E fromElement) 返回一个新的集合,新集合包含原集合中 fromElement 对象之后的所有对 象。包含 fromElement 对象

例 2

本次有 5 名学生参加考试,当老师录入每名学生的成绩后,程序将按照从低到高的排列顺序显示学生成绩。此外,老师可以查询本次考试是否有满分的学生存在,不及格的成绩有哪些,90 分以上成绩的学生有几名。

下面使用 TreeSet 类来创建 Set 集合,完成学生成绩查询功能。具体的代码如下:

public class Test08 {    public static void main(String[] args) {        TreeSet<Double> scores = new TreeSet<Double>(); // 创建 TreeSet 集合        Scanner input = new Scanner(System.in);        System.out.println("------------学生成绩管理系统-------------");        for (int i = 0; i < 5; i++) {            System.out.println("第" + (i + 1) + "个学生成绩:");            double score = input.nextDouble();            // 将学生成绩转换为Double类型,添加到TreeSet集合中            scores.add(Double.valueOf(score));        }        Iterator<Double> it = scores.iterator(); // 创建 Iterator 对象        System.out.println("学生成绩从低到高的排序为:");        while (it.hasNext()) {            System.out.print(it.next() + "\t");        }        System.out.println("\n请输入要查询的成绩:");        double searchScore = input.nextDouble();        if (scores.contains(searchScore)) {            System.out.println("成绩为: " + searchScore + " 的学生存在!");        } else {            System.out.println("成绩为: " + searchScore + " 的学生不存在!");        }        // 查询不及格的学生成绩        SortedSet<Double> score1 = scores.headSet(60.0);        System.out.println("\n不及格的成绩有:");        for (int i = 0; i < score1.toArray().length; i++) {            System.out.print(score1.toArray()[i] + "\t");        }        // 查询90分以上的学生成绩        SortedSet<Double> score2 = scores.tailSet(90.0);        System.out.println("\n90 分以上的成绩有:");        for (int i = 0; i < score2.toArray().length; i++) {            System.out.print(score2.toArray()[i] + "\t");        }    }}

如上述代码,首先创建一个 TreeSet 集合对象 scores,并向该集合中添加 5 个 Double 对象。接着使用 while 循环遍历 scores 集合对象,输出该对象中的元素,然后调用 TreeSet 类中的 contains() 方法获取该集合中是否存在指定的元素。最后分别调用 TreeSet 类中的 headSet() 方法和 tailSet() 方法获取不及格的成绩和 90 分以上的成绩。

运行该程序,执行结果如下所示。

------------学生成绩管理系统-------------
第1个学生成绩:
53
第2个学生成绩:
48
第3个学生成绩:
85
第4个学生成绩:
98
第5个学生成绩:
68
学生成绩从低到高的排序为:
48.0    53.0    68.0    85.0    98.0
请输入要查询的成绩:
90
成绩为: 90.0 的学生不存在!不及格的成绩有:
48.0    53.0
90 分以上的成绩有:
98.0

注意:在使用自然排序时只能向 TreeSet 集合中添加相同数据类型的对象,否则会抛出 ClassCastException 异常。如果向 TreeSet 集合中添加了一个 Double 类型的对象,则后面只能添加 Double 对象,不能再添加其他类型的对象,例如 String 对象等。

(8)Java final修饰符:final修饰属性、final修饰方法及final修饰类

final 关键字表示对象是最终形态的,对象是不可改变的意思。final 应用于类、方法和变量时意义是不同的,但本质是一样的:final 表示不可改变。

final 用在变量的前面表示变量的值不可以改变,此时该变量可以被称为常量;final 用在方法的前面表示方法不可以被重写;final 用在类的前面表示类不可以被继承,即该类是最终形态,如常见的 java.lang.String 类。

final 修饰符使用在如下方面:

1. final 修饰类中的属性

表示该属性一旦被初始化便不可改变,这里不可改变的意思对基本类型来说是其值不可变,而对对象属性来说其引用不可再变。其初始化可以在两个地方:一是其定义处,也就是说在 final 属性定义时直接给其赋值;二是在构造函数中。这两个地方只能选其一,要么在定义时给值,要么在构造函数中给值,不能同时既在定义时赋值,又在构造函数中赋予另外的值。

2. final 修饰类中的方法

说明这种方法提供的功能已经满足当前要求,不需要进行扩展,并且也不允许任何从此类继承的类来重写这种方法,但是继承仍然可以继承这个方法,也就是说可以直接使用。在声明类中,一个 final 方法只被实现一次。

3. final 修饰类

表示该类是无法被任何其他类继承的,意味着此类在一个继承树中是一个叶子类,并且此类的设计已被认为很完美而不需要进行修改或扩展。

对于 final 类中的成员,可以定义其为 final,也可以不是 final。而对于方法,由于所属类为 final 的关系,自然也就成了 final 型。也可以明确地给 final 类中的方法加上一个 final,这显然没有意义。

例 1

下面创建一个示例演示 final 修饰符的具体使用。首先新建 FinalTest.java 文件,在该文件中创建 FinalTest 类,定义一个声明为 final 的 count 属性和一个声明为 final 的 sum() 方法。

FinalTest 类的代码如下:

public class FinalTest {
final int count = 1;  public int updateCount() {
count = 4;    // 修改final属性值,提示错误    因为final 用在变量的前面表示变量的值不可以改变,此时该变量可以被称为常量,赋值一次就不能赋值第二次了
return count;    }  public final int sum() {
int number = count+10;
return number;
}}

创建 FinalTest 类的子类 FinalExtendTest,在该类中重写父类 FinalTest 中的 sum() 方法,并继承父类中的 sum() 方法。代码如下:

public class FinalExtendTest extends FinalTest {
public int sum(){};    // 重写父类 FinalTest 中的 sum()方法,出错  父类  sum()被final修饰就不能被子类重新,但是可以继承直接调用该办法
int count = sum();    // 继承父类 FinalTest 中的 sum()方法}

最后在 FinalExtend 类中新建程序主方法——main() 方法,实例化 FinalExtendTest 类并访问该类的 count 属性,代码如下所示。

public static void main(String[] args) {
FinalExtendTest fet = new FinalExtendTest();
System.out.println(fet.count);}

在编译该文件时,会出现两处错误:一是代码“count=4;”处,此处试图给 count 变量重新赋值,会产生错误,因为 final 变量只能被初始化一次。第二处是代码“public int sum(){};”,此处试图重写 final 修饰的方法,会出现错误,因为 final 修饰的方法可以被继承但不能被任何类重写。

将两处的错误语句都注释掉,程序运行后输出的结果如下:

11

(9)接口的定义和接口的实现,定义接口,实现接口

​ 切记接口不需要修饰符:String findById(int id);不必要写成 public String findById(int id);,因为它默认 public abstract String findById(int id);

  • 在 Java 接口中声明的变量其实都是常量,接口中的变量声明,将隐式地声明为 public、static 和 final,即常量,所以接口中定义的变量必须初始化。
  • 接口中:办法默认是 :public abstract 属性是 public abstract final 也就是接口的属性都是常量,办法都是抽象办法

接口类似于类,但接口的成员没有执行体,它只是方法、属性、事件和索引符的组合而已。接口不能被实例化,接口没有构造方法,没有字段。在应用程序中,接口就是一种规范,它封装了可以被多个类继承的公共部分。

定义接口

接口继承和实现继承的规则不同,一个类只有一个直接父类,但可以实现多个接口。Java 接口本身没有任何实现,只描述 public 行为,因此 Java 接口比 Java 抽象类更抽象化。Java 接口的方法只能是抽象的和公开的,Java 接口不能有构造方法,Java 接口可以有 public、static 和 final 属性。

接口把方法的特征和方法的实现分隔开来,这种分隔体现在接口常常代表一个角色,它包装与该角色相关的操作和属性,而实现这个接口的类便是扮演这个角色的演员。一个角色由不同的演员来演,而不同的演员之间除了扮演一个共同的角色之外,并不要求其他的共同之处。

接口对于其声明、变量和方法都做了许多限制,这些限制作为接口的特征归纳如下:

  • 具有 public 访问控制符的接口,允许任何类使用;没有指定 public 的接口,其访问将局限于所属的包。
  • 方法的声明不需要其他修饰符,在接口中声明的方法,将隐式地声明为公有的(public)和抽象的(abstract)。
  • 在 Java 接口中声明的变量其实都是常量,接口中的变量声明,将隐式地声明为 public、static 和 final,即常量,所以接口中定义的变量必须初始化。
  • 接口没有构造方法,不能被实例化。例如:
public interface A {
publicA(){…}    // 编译出错,接口不允许定义构造方法}
  • 一个接口不能够实现另一个接口,但它可以继承多个其他接口。子接口可以对父接口的方法和常量进行重写。例如:
public interface StudentInterface extends PeopleInterface {
// 接口 StudentInterface 继承 PeopleInterface
int age = 25;    // 常量age重写父接口中的age常量
void getInfo();    // 方法getInfo()重写父接口中的getInfo()方法
}

Java 接口的定义方式与类基本相同,不过接口定义使用的关键字是 interface,接口定义由接口声明和接口体两部分组成。语法格式如下:

[public] interface interface_name [
extends interface1_name[, interface2_name,…]] {// 接口体,其中可以包含定义常量和声明方法[public] [static] [final] type constant_name = value;    // 定义常量[public] [abstract] returnType method_name(parameter_list);    // 声明方法
}

其中,public 表示接口的修饰符,当没有修饰符时,则使用默认的修饰符,此时该接口的访问权限仅局限于所属的包;interfaCe_name 表示接口的名称,可以是任何有效的标识符;extends 表示接口的继承关系;interface1_name 表示要继承的接口名称;constant_name 表示变量名称,一般是 static 和 final 型的;returnType 表示方法的返回值类型;parameter_list 表示参数列表,在接口中的方法是没有方法体的。

提示:如果接口本身被定义为 public,则所有的方法和常量都是 public 型的。

例如,定义一个接口 MyInterface,并在该接口中声明常量和方法,如下:

public interface MyInterface {    // 接口myInterface
String name;    // 不合法,变量name必须初始化
int age = 20;    // 合法,等同于 public static final int age=20;
void getInfo();    // 方法声明,等同于 public abstract void getInfo();}

实现接口

接口被定义后,一个或者多个类都可以实现该接口,这需要在实现接口的类的定义中包含 implements 子句,然后实现由接口定义的方法。实现接口的一般形式如下:

<public> class <class_name> [extends superclass_name] [implements interface[, interface…]] {//主体
}

如果一个类实现多个接口,这些接口需要使用逗号分隔。如果一个类实现两个声明了同样方法的接口,那么相同的方法将被其中任一个接口使用。实现接口的方法必须声明为 public,而且实现方法的类型必须严格与接口定义中指定的类型相匹配。

例 1

在程序的开发中,需要完成两个数的求和运算和比较运算功能的类非常多。那么可以定义一个接口来将类似功能组织在一起。下面创建一个示例,具体介绍接口的实现方式。

1)创建一个名称为 IMath 的接口,代码如下:

public interface IMath {    public int sum();    // 完成两个数的相加    public int maxNum(int a,int b);    // 获取较大的数}

2)定义一个 MathClass 类并实现 IMath 接口,MathClass 类实现代码如下:

public class MathClass implements IMath {
private int num1;    // 第 1 个操作数
private int num2;    // 第 2 个操作数
public MathClass(int num1,int num2) {
// 构造方法        this.num1 = num1;
this.num2 = num2;    }    // 实现接口中的求和方法
public int sum() {
return num1 + num2;    }    // 实现接口中的获取较大数的方法
public int maxNum(int a,int b) {
if(a >= b) {
return a;   }
else {
return b;
}
}}

在实现类中,所有的方法都使用了 public 访问修饰符声明。无论何时实现一个由接口定义的方法,它都必须实现为 public,因为接口中的所有成员都显式声明为 public。

3)最后创建测试类 NumTest,实例化接口的实现类 MathClass,调用该类中的方法并输出结果。该类内容如下:

public class NumTest {
public static void main(String[] args) {
// 创建实现类的对象
MathClass calc = new MathClass(100, 300);
System.out.println("100 和 300 相加结果是:" + calc.sum());
System.out.println("100 比较 300,哪个大:" + calc.maxNum(100, 300));
}}

程序运行结果如下所示。

100 和 300 相加结果是:400
100 比较 300,哪个大:300

在该程序中,首先定义了一个 IMath 的接口,在该接口中只声明了两个未实现的方法,这两个方法需要在接口的实现类中实现。在实现类 MathClass 中定义了两个私有的属性,并赋予两个属性初始值,同时创建了该类的构造方法。因为该类实现了 MathClass 接口,因此必须实现接口中的方法。在最后的测试类中,需要创建实现类对象,然后通过实现类对象调用实现类中的方法。

(10)Java利用内部类实现多重继承

多重继承指的是一个类可以同时从多于一个的父类那里继承行为和特征,然而我们知道 Java 为了保证数据安全,只允许单继承。

有些时候我们会认为如果系统中需要使用多重继承,那往往都是糟糕的设想,这时开发人员往往需要思考的不是怎么使用多重继承,而是他的设计是否存在问题。但是,有时候开发人员确实需要实现多重继承,而且现实生活中真正地存在这样的情况,例如遗传,我们既继承了父亲的行为和特征,也继承了母亲的行为和特征。

Java 提供的两种方法让我们实现多重继承:接口和内部类。

例 1

本节我们以生活中常见的遗传例子进行介绍,如儿子(或者女儿)是如何利用多重继承来继承父亲和母亲的优良基因的。

1)创建 Father 类,在该类中添加 strong() 方法。代码如下:

public class Father {
public int strong() {
// 强壮指数
return 9;
}}

2)创建 Mother 类,在该类中添加 kind() 方法。代码如下:

public class Mother {
public int kind() {            // 友好指数
return 8;    }}

3)重点在于儿子类的实现,创建 Son 类,在该类中通过内部类实现多重继承。代码如下:

public class Son {    // 内部类继承Father类
class Father_1 extends Father {      public int strong() {
return super.strong() + 1;
}    }
class Mother_1 extends Mother {     public int kind() {
return super.kind() - 2;
}    }    public int getStrong() {
return new Father_1().strong();
}    public int getKind() {
return new Mother_1().kind();
}}

上述代码定义两个内部类,这两个内部类分别继承 Father(父亲)类和 Mother(母亲)类,且都可以获取各自父类的行为。这是内部类一个很重要的特性:内部类可以继承一个与外部类无关的类,从而保证内部类的独立性。正是基于这一点,多重继承才会成为可能。

4)创建 Test 类进行测试,在 main() 方法中实例化 Son 类的对象,然后分别调用该对象的 getStrong() 方法和 getKind() 方法。代码如下:

public class Test {
public static void main(String[] args) {
Son son = new Son();
System.out.println("Son 的强壮指数:" + son.getStrong());
System.out.println("Son 的友好指数:" + son.getKind());
}}

执行上述代码,输出结果如下:

Son 的强壮指数:10
Son 的友好指数:6

从实现代码和输出结果可以发现,儿子继承父类,变得比父亲更加强壮;同时也继承了母类,只不过友好指数下降。

(11)笔记

Java入门知识点--老王笔记相关推荐

  1. Java小结|Java入门知识点

    Java入门知识点 Java入门知识点 Java的起源与演变 Java的起源 Java的演变 Java 体系与特点 Java体系 Java能做什么 Java的特性 Java 跨平台原理 Java 技术 ...

  2. Java 的锁-老王女儿的爱情

    对象锁: new一个对象,都会给这个实例创建一把锁,对象中的方法必须在实例创建后,通过调用方法获取锁,一个线程进去这个方法之前拿到对象的锁,才能调用方法,否则被阻塞,举个例子,老王有个如花似玉的女儿, ...

  3. 创业撸Java多年,老王准备换50万的车了

    大家好,我是锋哥,今天一个老朋友找我聊聊天,说最近几年事业稳定,准备换个50万的车. 我推荐他黑色奔驰GLC 300 这个学员比我厉害,我现在开的还是小英朗,还是手动的,哈哈!不过等过几年,也要换BB ...

  4. java二进制算法教程_关于JAVA入门二进制课程的笔记

    由于JAVA二进制基础那节课程讲解实在低于应该有的教学水平,故在此写下笔记. *虽然是免费课程,但是我不认为这是一个该有的[教学]课程,我是一个很认真的人,所以我决定自己做笔记,自己找资料进行学习,这 ...

  5. 《java入门基础》读书笔记

    小结 1.一个类的定义可以包含在另一个类中,这意味着一个.java文件可以包含多个类的定义.这种情况下的文件名与外层类的类名相同. 2.当从一个包中导入的一个类名与自身的类名一致时会发生名称冲突.当发 ...

  6. Java入门知识点,xxxxxx

    一.Java从键盘输入的三种方法 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStre ...

  7. javascript 老王开车去东北

    [Decode error - output not utf-8] 魔女 飞 奔驰 去 华南 [Finished in 1.1s] 需要变化的对象进行隔离.正是编程的乐趣之处 /*** by Jack ...

  8. java 从入门到单身狗_java 的23种设计模式 之单身狗和隔壁老王的故事

    觉得代码写的别扭了,回头翻翻java 的23种设计模式. today,额,这么晚了,困了.就弄个最简单的单例模式吧. 单例模式:俗称单身狗 package singleton; public clas ...

  9. 整理的java基础知识点笔记

    java基础知识点 (涉及到图片的资源因为在电脑本地,挨个挨个找太浪费时间就不找了) 基础点 **字节:**每逢8位是一个字节,这是数据存储的最小单位. 计算机中的数据转换: ​ 1 Byte = 8 ...

  10. Java入门到架构师知识点整理,P8的技术大咖是这样通关的

    一个人最怕的不是路途遥远,而是看不到胜利曙光.我希望下面这篇文章能给你的学习之路带来一丝曙光,大家不妨试着读一下吧,如果有收获给我点个赞哟. 温馨提醒:这篇文章写着写着就一万字了,建议大家关注后再收藏 ...

最新文章

  1. 大学c语言11页,C语言程序设计题库(11页)-原创力文档
  2. 文末惊喜福利 | 盘点2021主流架构创新实践
  3. 网格分割算法(Random Walks)
  4. matlab apfc,APFC-Boost 带APFC的Boost升压变换器在Matlab中的仿真实现 - 下载 - 搜珍网...
  5. Firefox 有 6 成用户仍使用 Add-On 扩展
  6. java导出excel多个sheet_java导出Excel多个工作表(添加多个sheet)
  7. caffe学习路的起点
  8. Unity3D的LightProbe动态光探头用法介绍
  9. 集团性企业数据信息系统解决方案
  10. 哥写的不是代码,是寂寞
  11. Python imageio方法示例
  12. 机器码、序列号、认证码、注册码的生成算法(一)
  13. 机械制图类毕业论文文献有哪些?
  14. matlab 绘图 模板,【科研绘图】MATLAB可视化代码模板
  15. Vue项目(Ant Design of Vue)踩坑记之——表格header可伸缩
  16. 认识Power BI
  17. Arduino ESP32 深度睡眠与外部唤醒(EXT0)
  18. Java之ip地址存储的数据类型
  19. ava.lang.IllegalStateException: Failed to introspect Class [xxxxxxxxImpl] from ClassLoader-Autowired
  20. 领英这样加人,一个月轻松加5000人

热门文章

  1. 拉格朗日方程的三种推导方法之基于欧拉-拉格朗日方程推导
  2. 我的故事登上了Android开发者的官网
  3. Rational Rose2003安装
  4. 牛顿与莱布尔茨的微积分战争
  5. 教你如何迅速秒杀掉:99%的海量数据处理面试题 1
  6. CodeSmith链接Oracle、MySQL数据库
  7. IDEA生成SerialVersionUID
  8. VTK:交互与Widget——观察者/命令模式
  9. html制作dnf,DNF教你如何不花一分钱制作90顶级史诗
  10. 32位java jre_64位的jre和32位的jre