JAVAOOP
在前面学习集合时,我们都知道集合中是可以存放任意对象的,只要把对象存储集合后,那么这时他们都会被提升成Object类型。当我们在取出每一个对象,并且进行相应的操作,这时必须采用类型转换。 大家观察下面代码: 程序在运行时发生了问题**jav
二、泛型
2.1 泛型概述
在前面学习集合时,我们都知道集合中是可以存放任意对象的,只要把对象存储集合后,那么这时他们都会被提升成Object类型。当我们在取出每一个对象,并且进行相应的操作,这时必须采用类型转换。
大家观察下面代码:
public class GenericDemo {
public static void main(String[] args) {
Collection coll = new ArrayList();
coll.add("abc");
coll.add("cba");
coll.add(5);//由于集合没有做任何限定,任何类型都可以给其中存放
Iterator it = coll.iterator();
while(it.hasNext()){
//需要打印每个字符串的长度,就要把迭代出来的对象转成String类型
String str = (String) it.next();
System.out.println(str.length());
}
}
}
程序在运行时发生了问题java.lang.ClassCastException。为什么会发生类型转换异常呢? 我们来分析下:由于集合中什么类型的元素都可以存储。导致取出时强转引发运行时 ClassCastException。
怎么来解决这个问题呢?
- 集合中可以存储各种对象,但实际上通常集合只存储同一类型对象。例如都是存储字符串对象。因此在JDK5之后,新增了泛型(Generic)语法,让你在设计API时可以指定类或方法支持泛型,这样我们使用API的时候也变得更为简洁,并得到了编译时期的语法检查。
- 泛型:可以在类或方法中预支地使用未知的类型。
tips:一般在创建对象时,将未知的类型确定具体的类型。当没有指定泛型时,默认类型为Object类型。
2.2 使用泛型的好处
泛型带来了哪些好处呢?
- 将运行时期的ClassCastException,转移到了编译时期变成了编译失败。
- 避免了类型强转的麻烦。
通过我们如下代码体验一下:
public class GenericDemo2 {
public static void main(String[] args) {
Collection<String> list = new ArrayList<String>();
list.add("abc");
list.add("cba");
// list.add(5);//当集合明确类型后,存放类型不一致就会编译报错
// 集合已经明确具体存放的元素类型,那么在使用迭代器的时候,迭代器也同样会知道具体遍历元素类型
Iterator<String> it = list.iterator();
while(it.hasNext()){
String str = it.next();
//当使用Iterator<String>控制元素类型后,就不需要强转了。获取到的元素直接就是String类型
System.out.println(str.length());
}
}
}
tips:泛型是数据类型的一部分,我们将类名与泛型合并一起看做数据类型。
2.3 泛型的定义与使用
泛型:是一种把类型明确的工作推迟到创建对象或者调用方法的时候才去明确的特殊的类型。参数化类型,把类型当作参数一样的传递。
我们在集合中会大量使用到泛型,这里来完整地学习泛型知识。泛型,用来灵活地将数据类型应用到不同的类、方法、接口当中。将数据类型作为参数进行传递。
2.3.1 泛型类
定义格式
- <数据类型> 此处的数据类型只能是引用类型。好处:
A:把运行时期的问题提前到了编译期间
B:避免了强制类型转换
自定义泛型类
/*
* 泛型类:把泛型定义在类上
*/
public class ObjectTool<T> {
private T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
}
如何使用泛型类
- 使用泛型: 即什么时候确定泛型。在创建对象的时候确定泛型
/*
* 泛型类的测试
*/
public class ObjectToolDemo {
public static void main(String[] args) {
ObjectTool<String> ot = new ObjectTool<String>();
ot.setObj(new String("江一燕"));
String s = ot.getObj();
System.out.println("姓名是:" + s);
ObjectTool<Integer> ot2 = new ObjectTool<Integer>();
ot2.setObj(27);
Integer i = ot2.getObj();
System.out.println("年龄是:" + i);
}
}
2.3.2 泛型方法
定义格式
- 修饰符 <代表泛型的变量> 返回值类型 方法名 (参数){ }
定义泛型方法
/*
* 泛型方法:把泛型定义在方法上
*/
public class ObjectTool {
public <T> Object show(T t) {
//System.out.println(t);
return t;
}
//定义一个泛型方法
//返回类型不应该明确,因为泛型方法的类型都不明确
//建议: Object 或者 T(泛型)
public <T> T show(T t){
System.out.println(t);
return t;
}
}
泛型方法的使用
- 调用方法时,确定泛型的类型
public class ObjectToolDemo {
public static void main(String[] args) {
// 定义泛型方法后
ObjectTool ot = new ObjectTool();
String s = (String) ot.show("hello");
Integer i = (Integer) ot.show(200);
Boolean b = (Boolean) ot.show(true);
System.out.println(s);
System.out.println(i);
System.out.println(b);
ObjectToolMethod otm = new ObjectToolMethod();
String aa = otm.show("aa");
Integer show = otm.show(11);
System.out.println(aa);
System.out.println(show);
}
}
2.3.3 泛型接口
定义格式
- 修饰符 interface接口名<代表泛型的变量> { }
定义泛型接口
/*
* 泛型接口:把泛型定义在接口上
*/
public interface Inter<T> {
public abstract void show(T t);
}
泛型接口的使用
- 实现类
//实现类在实现接口的时候
//第一种情况:已经知道该是什么类型的了
public class InterImpl implements Inter<String> {
@Override
public void show(String t) {
System.out.println(t);
}
}
//第二种情况:还不知道是什么类型的
public class InterImpl<T> implements Inter<T> {
@Override
public void show(T t) {
System.out.println(t);
}
}
测试
2.4 泛型通配符
2.4.1 什么是泛型通配符
当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。
2.4.2 通配符基本使用
泛型的通配符:不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符。
此时只能接受数据,不能往该集合中存储数据。
2.4.3 通配符高级使用
/*
* 泛型高级(通配符)
* ?:任意类型,如果没有明确,那么就是Object以及任意的Java类了
* ? extends E:向下限定,E及其子类
* ? super E:向上限定,E极其父类
*/
public class GenericDemo {
public static void main(String[] args) {
// 泛型如果明确的写的时候,前后必须一致
Collection<Object> c1 = new ArrayList<Object>();
// Collection<Object> c2 = new ArrayList<Animal>();
// Collection<Object> c3 = new ArrayList<Dog>();
// Collection<Object> c4 = new ArrayList<Cat>();
// ?表示任意的类型都是可以的
Collection<?> c5 = new ArrayList<Object>();
Collection<?> c6 = new ArrayList<Animal>();
Collection<?> c7 = new ArrayList<Dog>();
Collection<?> c8 = new ArrayList<Cat>();
// ? extends E:向下限定,E及其子类,表示包括E在内的任何子类;
// Collection<? extends Animal> c9 = new ArrayList<Object>();
Collection<? extends Animal> c10 = new ArrayList<Animal>();
Collection<? extends Animal> c11 = new ArrayList<Dog>();
Collection<? extends Animal> c12 = new ArrayList<Cat>();
// ? super E:向上限定,E极其父类,表示包括E在内的任何父类;
Collection<? super Animal> c13 = new ArrayList<Object>();
Collection<? super Animal> c14 = new ArrayList<Animal>();
// Collection<? super Animal> c15 = new ArrayList<Dog>();
// Collection<? super Animal> c16 = new ArrayList<Cat>();
}
}
class Animal {
}
class Dog extends Animal {
}
class Cat extends Animal {
}
2.5. 静态导入-了解
/*
* 静态导入:
* 格式:import static 包名….类名.方法名;
* 可以直接导入到方法的级别
*
* 静态导入的注意事项:
* A:方法必须是静态的
* B:如果有多个同名的静态方法,容易不知道使用谁?这个时候要使用,必须加前缀。由此可见,意义不大,所以一般不用,但是要能看懂。
*/
import static java.lang.Math.abs;
import static java.lang.Math.pow;
import static java.lang.Math.max;
public class StaticImportDemo {
public static void main(String[] args) {
// System.out.println(java.lang.Math.abs(-100));
// System.out.println(java.lang.Math.pow(2, 3));
// System.out.println(java.lang.Math.max(20, 30));
// 太复杂,我们就引入到import
// System.out.println(Math.abs(-100));
// System.out.println(Math.pow(2, 3));
// System.out.println(Math.max(20, 30));
// 太复杂,有更简单
System.out.println(abs(-100));
System.out.println(pow(2, 3));
System.out.println(max(20, 30));
}
}
2.6. 数组集合互转-可变参
/*
* public static <T> List<T> asList(T... a):把数组转成集合
*
* 注意事项:
* 虽然可以把数组转成集合,但是集合的长度不能改变。
*/
public class ArraysDemo {
public static void main(String[] args) {
//数组 -- list集合
String[] str = {"hello","world","java"};
List<String> list = Arrays.asList(str);
System.out.println(list);
//数组 -- set集合
String[] str2 = {"hello","world","java"};
Set<String> set = Arrays.stream(str2).collect(Collectors.toSet());
System.out.println(set);
//可变参数(T...a)
Arrays.asList("hello","world","yiyan", 123, true);
//注意事项
List<String> list1 = Arrays.asList("hello", "world", "java");
// list1.add("jack");//本质还是数组长度,类型不能改变
// list1.add("rose");
//可以修改元素
list1.set(1,"rose");
System.out.println(list1);
}
}
public static void main(String[] args) {
//list集合 --> 数组
List<String> list = new ArrayList<String>();
list.add("hello");
list.add("world");
//定义数组的长度
String[] str = new String[list.size()];
list.toArray(str);//添加元素
//System.out.println(str[0]);
//System.out.println(str[1]);
for(String s : str){
System.out.println(s);
}
//set集合 -- 数组
Set<String> set = new HashSet<>();
set.add("abc");
set.add("cba");
set.add("cba");
//先转成list
List<String> lists = new ArrayList<>(set);
//list转数组
String[] str4 = new String[lists.size()];
lists.toArray(str4);
System.out.println(str4[0]);
System.out.println(str4[1]);
for(String s : str4){
System.out.println(s);
}
}
三、Collections
3.1 什么是Collections
Collections是针对集合操作的工具类
3.2 Collections常用的方法
| 方法名 | 说明 |
|---|---|
| public static void sort(List list) | 将指定的列表按升序排序 |
| public static void reverse(List<?> list) | 反转指定列表中元素的顺序 |
| public static void max() | 获取集合中最大值 |
3.3 Collections代码演示
public class CollectionsDemo01 {
public static void main(String[] args) {
//创建集合对象
List<Integer> list = new ArrayList<Integer>();
//添加元素
list.add(30);
list.add(20);
list.add(50);
list.add(10);
list.add(40);
// 将指定的列表按升序排序
// Collections.sort(list);
//反转指定列表中元素的顺序
// Collections.reverse(list);
//获取集合是最大值
System.out.println("最大值:"+Collections.max(list));
System.out.println(list);
}
}
3.4. collection和collectons的区别?
Collection 是一个集合接口 它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。
Collections 是一个包装类 是一个包装类。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于Java的Collection框架。
四、File类
4.1 什么是File类
java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等操作。
4.2 File类构造方法
| 方法名 | 说明 |
|---|---|
| public File(String pathname) | 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。 |
| public File(String parent, String child) | 从父路径名字符串和子路径名字符串创建新的 File实例。 |
| public File(File parent, String child) | 从父抽象路径名和子路径名字符串创建新的 File实例。 |
代码演示
public class FileDemo{
public static void main(String[] args){
// 文件路径名
String pathname = "D:\\aaa.txt";
File file1 = new File(pathname);
// 文件路径名
String pathname2 = "D:\\aaa\\bbb.txt";
File file2 = new File(pathname2);
// 通过父路径和子路径字符串
String parent = "d:\\aaa";
String child = "bbb.txt";
File file3 = new File(parent, child);
// 通过父级File对象和子路径字符串
File parentDir = new File("d:\\aaa");
String child = "bbb.txt";
File file4 = new File(parentDir, child);
}
}
注意事项
一个File对象代表硬盘中实际存在的一个文件或者目录。
无论该路径下是否存在文件或者目录,都不影响File对象的创建。
4.3 获取功能方法
| 方法名 | 说明 |
|---|---|
| public String getAbsolutePath() | 返回此File的绝对路径名字符串。 |
| public String getPath() | 将此File转换为路径名字符串 |
| public String getName() | 返回由此File表示的文件或目录的名称。 |
| public long length() | 返回由此File表示的文件的长度。 |
代码演示
public class FileGet {
public static void main(String[] args) {
File f = new File("d:/aaa/bbb.java");
System.out.println("文件绝对路径:"+f.getAbsolutePath());
System.out.println("文件构造路径:"+f.getPath());
System.out.println("文件名称:"+f.getName());
System.out.println("文件长度:"+f.length()+"字节");
File f2 = new File("d:/aaa");
System.out.println("目录绝对路径:"+f2.getAbsolutePath());
System.out.println("目录构造路径:"+f2.getPath());
System.out.println("目录名称:"+f2.getName());
System.out.println("目录长度:"+f2.length());
}
}
4.4 绝对路径和相对路径
- 绝对路径:从盘符开始的路径,这是一个完整的路径。
- 相对路径:相对于项目目录的路径,这是一个便捷的路径,开发中经常使用。
public class FilePath {
public static void main(String[] args) {
// D盘下的bbb.java文件
File f = new File("D:\\bbb.java");
System.out.println(f.getAbsolutePath());
// 项目下的bbb.java文件
File f2 = new File("day10\\bbb.java");
System.out.println(f2.getAbsolutePath());
}
}
4.5 判断功能的方法
| 方法名 | 说明 |
|---|---|
| public boolean exists() | 此File表示的文件或目录是否实际存在。 |
| public boolean isDirectory() | 此File表示的是否为目录。 |
| public String getName() | 返回由此File表示的文件或目录的名称。 |
代码演示
public class FileIs {
public static void main(String[] args) {
File file1 = new File("a.txt");
System.out.println("是不是一个目录:"+file1.isDirectory());
System.out.println("是不是一个文件:"+file1.isFile());
System.out.println("是否能写:"+file1.canWrite());
System.out.println("是否能读:"+file1.canRead());
System.out.println("是否存:"+file1.exists());
System.out.println("是隐藏:"+file1.isHidden());
}
}
4.6 创建删除功能的方法
| 方法名 | 说明 |
|---|---|
| public boolean createNewFile() | 当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。 |
| public boolean delete() | 删除由此File表示的文件或目录。 |
| public boolean mkdir() | 创建由此File表示的目录。 |
| public boolean mkdirs() | 创建由此File表示的目录,包括任何必需但不存在的父目录。 |
代码演示
public class FileCreateDelete {
public static void main(String[] args) throws IOException {
//项目的根目录下
File file1 = new File("a.txt");
System.out.println(file1.createNewFile());
//删除文件
File file2 = new File("a.txt");
System.out.println(file2.delete());
//创建文件夹
File file3 = new File("aa");
System.out.println( file3.mkdir());
//删除文件夹
File file4 = new File("aa");
System.out.println(file4.delete());
//创建多级文件夹
File file5 = new File("aa\\bb\\cc");
System.out.println( file5.mkdirs());
//删除多级文件夹
//注意:删除多级目录只能从里一级一级的进行删除
File file6 = new File("aa\\bb\\cc");
System.out.println(file6.delete());
}
}
注意事项
delete方法,如果此File表示目录,则目录必须为空才能删除。
4.7. 重命名功能
/*
* 重命名功能:public boolean renameTo(File dest)
* 如果路径名相同,就是改名。
* 如果路径名不同,就是改名并剪切。
*
* 路径以盘符开始:绝对路径 c:\\a.txt
* 路径不以盘符开始:相对路径 a.txt
*/
public static void main(String[] args) throws IOException {
//数据数据源,前提得这个数据存在
File srcFile = new File("D:\\江一燕.jpg");
//数据数据的目的地
File newFile = new File("D:\\我和江一燕.jpg");//相同路径是改名
// File newFile2 = new File("e:\\我和江一燕.jpg");//不同路径是剪切并改名
//实现改名
System.out.println(srcFile.renameTo(newFile));
}
}
4.7 目录的遍历
| 方法名 | 说明 |
|---|---|
| public String[] list() | 返回一个String数组,表示该File目录中的所有子文件或目录。 |
| public File[] listFiles() | 返回一个File数组,表示该File目录中的所有的子文件或目录。 |
public class FileFor {
public static void main(String[] args) {
//创建file
File file = new File("d:\\");
//获取e盘下所有的文件及目录
String[] list = file.list();
for(String s : list){
System.out.println(s);
}
System.out.println("------------------");
//创建file
File file2 = new File("d:\\");
//获取e盘下的所有file文件
File[] files = file2.listFiles();
for(File f : files){
System.out.println(f);
}
}
}
注意事项
调用listFiles方法的File对象,表示的必须是实际存在的目录,否则返回null,无法进行遍历。
作业:判断E盘目录下是否有后缀名为.jpg的文件,如果有,就输出此文件名称
五、 递归
5.1 什么是递归
指在当前方法内调用自己的这种现象。
从前有座山,山里有座庙,庙里有一个老和尚和一个小和尚。。。。。。
你为什么要放羊,为了娶媳妇,娶媳妇干什么,为了生娃娃。。。。。。
5.2 递归的分类
递归分为两种,直接递归 和 间接递归。
直接递归:称为方法自身调用自己。
间接递归:可以A方法调用B方法,B方法调用C方法,C方法调用A方法。
5.3 递归的注意事项
递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出。
在递归中虽然有限定条件,但是递归次数不能太多。否则也会发生栈内存溢出。
构造方法,禁止递归
package cn.yanqi_02;
public class Demo01DiGui {
/*
* 3.构造方法,禁止递归
* 编译报错:构造方法是创建对象使用的,不能让对象一直创建下去
*/
public Demo01DiGui() {
//Demo01DiGui();
}
public static void main(String[] args) {
show1();
// show2(1);
}
/*
* 2.在递归中虽然有限定条件,但是递归次数不能太多。否则也会发生栈内存溢出。
* 4993
* Exception in thread "main" java.lang.StackOverflowError
*/
private static void show2(int i) {
System.out.println(i);
//添加一个递归结束的条件,i==5000的时候结束
if(i==5000){
return;//结束方法
}
show2(++i);
}
/*
* 1.递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出。 Exception in thread "main"
* java.lang.StackOverflowError
*/
private static void show1() {
System.out.println("a方法");
show1();
}
}
5.4 递归累加求和
需求
- 计算1 ~ n的和
分析
- num的累和 = num + (num-1)的累和,所以可以把累和的操作定义成一个方法,递归调用。
代码实现
public class DiGuiDemo {
public static void main(String[] args) {
//计算1~num的和,使用递归完成
int num = 5;
// 调用求和的方法
int sum = getSum(num);
// 输出结果
System.out.println(sum);
}
/*
通过递归算法实现.
参数列表:int
返回值类型: int
*/
public static int getSum(int num) {
/*
num为1时,方法返回1,
相当于是方法的出口,num总有是1的情况
*/
if(num == 1){
return 1;
}
/*
num不为1时,方法返回 num +(num-1)的累和
递归调用getSum方法
*/
int gs = getSum(num-1);
return num + gs;
}
}
5.5 递归求阶乘
什么是阶乘
- 所有小于及等于该数的正整数的积。使用递归求5的阶乘
5! = 5 * 4 * 3 * 2 * 1
5! = 5 * 4!
4! = 4 * 3!
3! = 3 * 2!
2! = 2 * 1!
1! = 1
求n的阶乘
n * (n - 1)!
求N阶乘公式
- n! = n _ (n-1) ... 3 _ 2 * 1
分析
- 这与累和类似,只不过换成了乘法运算,学员可以自己练习,需要注意阶乘值符合int类型的范围。推理得出:n! = n * (n-1)!
代码实现
public class DiGuiDemo {
//计算n的阶乘,使用递归完成
public static void main(String[] args) {
int n = 3;
// 调用求阶乘的方法
int value = getValue(n);
// 输出结果
System.out.println("阶乘为:"+ value);
}
/*
通过递归算法实现.
参数列表:int
返回值类型: int
*/
public static int getValue(int n) {
// 1的阶乘为1
if (n == 1) {
return 1;
}
/*
n不为1时,方法返回 n! = n*(n-1)!
递归调用getValue方法
*/
return n * getValue(n - 1);
}
}

六、综合案例
6.1 打印多级目录
分析
- 多级目录的打印,就是当目录的嵌套。遍历之前,无从知道到底有多少级目录,所以我们还是要使用递归实现。
代码实现
public class DiGuiDemo2 {
public static void main(String[] args) {
// 创建File对象
File dir = new File("E:\workspase\507\day13");
// 调用打印目录方法
printDir(dir);
}
public static void printDir(File dir) {
// 获取子文件和目录
File[] files = dir.listFiles();
// 循环打印
/*
判断:
当是文件时,打印绝对路径.
当是目录时,继续调用打印目录的方法,形成递归调用.
*/
for (File file : files) {
// 判断
if (file.isFile()) {
// 是文件,输出文件绝对路径
System.out.println("文件名:"+ file.getAbsolutePath());
} else {
// 是目录,输出目录绝对路径
System.out.println("目录:"+file.getAbsolutePath());
// 继续遍历,调用printDir,形成递归
printDir(file);
}
}
}
}
6.2 文件搜索
需求
- 搜索D:\aaa 目录中的.java 文件。
需求分析
- 目录搜索,无法判断多少级目录,所以使用递归,遍历所有目录。遍历目录时,获取的子文件,通过文件名称,判断是否符合条件。
代码实现
public class DiGuiDemo3 {
public static void main(String[] args) {
// 创建File对象
File dir = new File("D:\\aaa");
// 调用打印目录方法
printDir(dir);
}
public static void printDir(File dir) {
// 获取子文件和目录
File[] files = dir.listFiles();
// 循环打印
for (File file : files) {
if (file.isFile()) {
// 是文件,判断文件名并输出文件绝对路径
if (file.getName().endsWith(".java")) {
System.out.println("文件名:" + file.getAbsolutePath());
}
} else {
// 是目录,继续遍历,形成递归
printDir(file);
}
}
}
}