文章目录
基础知识漏洞
1.==和equals()的区别
- == 和 equals 的区别
- 对于 == ,如果作用于基本数据类型的变量,则直接比较存储的 ‘值’ 是否相等,如果作用于引用类型的变量,则比较的是所指向对象的地址
- 对于 equals() 方法,注意:equals() 方法不能作用于基本数据类型的变量
- 如果没有对 equals 方法进行重写,则比较的是引用类型的变量所指向的对象的地址;
- 如:String、Date、等类对equals方法进行了重写的话,比较的是所指向的对象的内容。
2.枚举类型
🍶定义:
- 枚举可以单独定义在一个文件中,也可以嵌在其他 Java 类中。
- 通过本例可以看到 enum 提供的 values()方法可以用于遍历所有枚举值, oridinal()方法可以用于返回枚举值在枚举类中的顺序。
package cn.itcast;
//枚举可以定义在类文件中
enum Season{春,夏,秋,冬};
public class Test{
//枚举也可以定义在这里
enum Season{春,夏,秋,冬};
public static void main(String[] args){
//枚举定义在main方法中会报错
//enum Season{春,夏,秋,冬}; -- 这是错误的
for(Season s:Season){
//输出枚举每个元素
System.out.print(s+",");//春,夏,秋,冬,
//输出枚举每个元素下标
System.out.print(s.ordinal()+",");//0,1,2,3,
}
}
}
🎈注意:
- 枚举类型不能有 public 的构造方法,但是可以定义 private 的构造方法在 enum 内部使用,这样做可以确保不能新建一个 enum 的实例。
- 所有枚举值都是 public,static,final 类型。
- 枚举可以实现一个或者多个接口(interface)。
- 枚举可以定义新的变量与方法。
- 匿名内部类 – 常规写法
package com.company; public class Main { public static void main(String[] args) { new Main(){ void sound(){ System.out.println("匿名内部类"); } }.sound(); } }
–控制台输出–:
匿名内部类
3. java 堆栈内存
int[] arr = new int[3]; 左边是堆内存 右边是栈内存 栈内存:存放局部变量 定义在方法中的变量 例如 arr 使用完毕,立即消失 堆内存:存储new出来的实体(实体,对象),数组初始化时,会为存储空间添加默认值 整数:0 浮点数:0.0 布尔:false 字符:空字符 引用数据类型:null 每new出来的东西都有一个地址值,会在垃圾回收器空闲时被回收
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jcYAqAOo-1642749515446)(C:\Users\lwx\AppData\Roaming\Typora\typora-user-images\image-20211019115009065.png)]
4. 基本类型和引用类型参数传递 改变值
public class TestDemo { public static void main(String [] args){ //定义引用类型参数变量 int[] arr = {10,20,30}; //定义基本类型参数变量 int a = 20; //基本类型形参传递 System.out.println("基本参数类型传递前:"+a);//20 change0(a); System.out.println("基本参数类型传递后:"+a);//20 //引用类型形参传递 System.out.println("调用前:"+arr[1]);//20 change(arr); System.out.println("调用后:"+arr[1]);//200 } //基本类型的参数传递 public static void change0(int a){ a=200; } //引用类型的参数传递 public static void change(int[] arr){ arr[1]=200; } }
–控制台输出–
基本参数类型传递前:20
基本参数类型传递后:20
调用前:20
调用后:200
5. 成员变量和局部变量
区别 | 成员变量 | 局部变量 |
---|---|---|
类中位置不同 | 类中方法外 | 方法内或者方法声明上 |
内存中位置不同 | 堆内存 | 栈内存 |
生命周期不同 | 随着对象的存在而存在,随着对象的消失而消失 | 随着方法的调用而存在,随着方法的调用完毕而消失 |
初始化值不同 | 有默认的初始化值 | 没有默认的初始化值,必须先定义,赋值,才能使用 |
6.集合基础
- 创建集合,集合增删改查,输出集合
package cn.itcast; import java.util.ArrayList; import java.util.Scanner; public class Test { public static void main(String[] args) { //创建一个集合 ArrayList<String> array = new ArrayList<>(); //添加三个元素 array.add("hello"); array.add("world"); array.add("java"); //指定位置添加元素 1 array.add(1,"javase"); System.out.println("array:"+array); //删除元素 array.remove("hello"); array.remove("2"); //修改元素 array.set(1,"hello1"); //返回指定索引处的元素 array.get(0); //集合长度 array.size(); } }
控制台输出:
array:[hello, javase, world, javaee, javae, java]
7. 继承(extends)
1.继承中子类成员方法变量的访问特点
在子类方法中访问一个变量 (就近原则)
- 子类局部范围找
- 子类成员范围找
- 父类成员范围找
- 如果都没有就报错(不考虑父亲的父亲)
2.继承中构造方法的访问特点
- 子类中所有的构造方法默认都会访问父类中无参的构造方法
- 为什么呢:
- 因为子类会继承父类中的数据,可能还会使用父类的数据,所以,子类初始化之前,一定要完成父类数据的初始化
- 每一个子类构造方法的第一条语句默认是:super()
- 如果父类中没有无参构造方法,只有带参构造方法,该怎么办
- 通过使用super关键词去显示的调用父类的带参构造方法
- 在父类中自己提供一个无参构造方法(推荐)
3.方法重写注意事项(Override)
- 私有方法不能被重写(父类private私有化的成员子类是无法继承的)
- 子类方法访问权限修饰不能比父类低(public > 默认 > 私有)
8.四种访问控制权限
修饰符 | 同一个类中 | 同一个包中子类无关类 | 不同包的子类 | 不同包的无关类 |
---|---|---|---|---|
私有(private) | √ | |||
默认(default) | √ | √ | ||
包(protected) | √ | √ | √ | |
公共(public) | √ | √ | √ | √ |
9.final修饰局部变量
- 变量是基本类型:final修饰指的是基本类型的 数据值 不能发生改变。
- 变量是引用类型:final修饰指的是引用类型的地址值不能发生改变,但是地址里面的内容是可以发生改变的。
10.static关键字
static 修饰的特点
- 被类的所有对象共享
- 这也是我们判断是否使用静态关键字的条件
- 可以通过类名调用(推荐使用类名调用)
- 当然,也可以通过对象名调用
static 访问特点(总结一句话:就是静态成员方法只能访问静态成员)
- 非静态的成员方法
- 能访问静态的成员变量,能访问非静态的成员变量,能访问静态的成员方法,能访问非静态的成员方法。
- 静态的成员方法
- 能访问静态的成员方法,能访问静态的成员方法。
11.多态
多态的形式:具体类多态,抽象类多态,接口多态
多态的前提:有继承或者实现的关系;有方法重写;有父(类/接口)引用指向(子/实现)类对象
1.成员变量,成员方法
//父类 public class Animal { public int age = 40; public void eat(){ System.out.println("动物吃东西"); } } //子类 public class Cat extends Animal{ public int age = 20; public int weight = 10; @Override public void eat() { System.out.println("猫吃鱼"); } public void palyGame(){ System.out.println("猫捉迷藏"); } } //执行类 public class Test { public static void main(String[] args) { Animal an = new Cat(); System.out.println(an.age);//这个可以输出,输出子类的20,而不是父类中的40。 System.out.println(an.weight);//这里会出现编译时期异常,因为多态情况下,对于成员变量:编译看左边(Animal an),运行也看左边。 an.eat();//输出子类中的 猫吃鱼 因为多态情况下:对于成员方法,编译看左边,运行看右边,也可以输,这是子类重写了父类的eat方法,所以最后运行子类的eat an.playGame();//编译异常,因为多态对于成员方法,编译看左边(Animal an),父类中没有playGame,那么就会报编译异常 } } 总结:多态中成员访问特点 成员变量:编译看左边,运行看左边 成员方法:编译看左边,执行看右边 为什么成员变量和成员方法的访问不一样呢? 因为成员方法有重写,而成员变量没有。
2.多态的应用及好处和弊端
//父类 public class Animal { public void eat(){ System.out.println("动物吃东西"); } } //动物操作类 public class AnimalOperator { //可以通过这个方法创建多个子类,然后继承父类,重写父类的方法,调用此函数传递不同的子类对象即可输出不同子类对应的成员方法 public void useAnimal(Animal a){ a.eat(); //调用子类特有的方法(狗类的特有方法,父类中不存在),报编译时期异常 a.lookDoor(); } } //子类(子类猫类) public class Cat extends Animal{ @Override public void eat() { System.out.println("猫吃鱼"); } } //子类(子类狗类) public class Dog extends Animal{ @Override public void eat() { System.out.println("狗吃肉"); } /* 注意:这里是子类特有的方法,并没有重写父类的方法 */ public void lookDoor(){ System.out.println("狗看门"); } } //执行类 public class Test { public static void main(String[] args) { //创建动物操作类的对象,调用方法 AnimalOperator al = new AnimalOperator(); Cat c = new Cat();//创建动物的子类 猫类 Dog d = new Dog();//创建动物的子类 狗类 al.useAnimal(d);//调用动物操作类的操作方法 将狗实例化对象传入 最终输出:狗看门 al.useAnimal(c);//调用动物操作类的操作方法 将猫实例化对象传入 最终输出:猫吃鱼 } } 总结: 多态的好处:可以从最后的执行类中看到,传递给动物操作类不同的子类对象,可以输出不同子类对象重写父类的成员方法 (提高了程序的扩展性,具体实现;定义方法的时候,使用父类作为参数,将来在使用的时候,使用具体的子类型参与操作) 多态的弊端:不能使用子类的特有功能。例如上面第32行的方法(lookDoor)定义,15行对子类特有方法调用,那么15行就会出现编译时期异常,因为父类中没有这个方法,对于成员方法在多态中,编译看左边,执行看右边,这里编译左边没有,就报错了。
3.向上转型和向下转型
//父类 public class Animal { public void eat(){ System.out.println("动物吃东西"); } } //子类 public class Cat extends Animal{ @Override public void eat() { System.out.println("猫吃鱼"); } //子类的特有方法 public void playGame(){ System.out.println("猫玩球儿"); } } //执行类 public class Test { public static void main(String[] args) { //向上转型:子类实例化用父类接收 Animal a = new Cat(); a.eat();//猫吃鱼 a.playGame();//编译异常这里,编译看左边,运行看右边。 //向下转型,先创建一个向上转型,然后把父类对象强制转换成子类对象的类型 Animal a2 = new Cat(); Cat c = (Cat)a2;//向上转型 c.playGame();// } }
12.抽象
特点:
抽象类中可以没有抽象方法,有抽象方法的类必须是抽象类
抽象类的子类要么也是抽象类(需要实现),要么重写父类中所有的抽象方法
抽象类无法直接实例化 但是可以通过多态的方式进行实例化👇
//抽象类 public abstract class Animal { public abstract void eat(); public void sleep(){ System.out.println("睡觉"); } } //继承抽象类的子类 public class Cat extends Animal{ @Override public void eat() { System.out.println("猫吃鱼"); } } //执行类 public class Test { public static void main(String[] args) { Animal a = new Cat(); a.eat();//猫吃鱼 a.sleep();//睡觉 } }
13.接口
接口使用interface修饰,类实现接口需要用implements表示
接口不能实例化,但是可以参照多态的方式,通过实现类对象实例化,这就是接口多态。👆
接口的实现类:1.要么重写接口中的所有抽象方法 2.要么是抽象类
1.接口的成员特点
- 成员变量
- 只能是常量(默认也是常量,子类无法修改)
- 默认修饰符:public static final
- 构造方法
- 接口没有构造方法,因为接口主要是对行为进行抽象的,是没有具体存在
- 一个类如果没有父类,默认继承Object类
- 成员方法
- 只能是抽象方法,jdk1.8之后可以通过default修饰用来定义普通方法
- 默认修饰符 public abstract
2.接口和类的关系
类和类的关系
继承关系,只能单继承,但是可以多层继承
public class Animal {}//爷爷类 public class Cat extends Animal{}//父类 public class catSon extends Cat{}//子类
类和接口的关系
实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
public class InterImpl extends InterOne implements Inter1,Inter2,Inter3{}
接口和接口的关系
继承关系,可以单继承,也可以多继承
public interface Inter1 extends Inter2,Inter3{}
3.抽象类和接口的区别
- 成员区别
- 抽象类 变量,常量;有构造方法;有抽象方法,也有非抽象方法
- 接口 常量,抽象方法
- 关系区别
- 类鱼类 继承,单继承
- 类与接口 实现,可以单实现,也可以多实现
- 接口与接口 继承,单继承,多继承
- 设计理念区别
- 抽象类 对类抽象,包括属性、行为
- 接口 对行为抽象,主要是行为
抽象类是对事物的抽象,接口是对行为的抽象
14.内部类
1.内部类实例
public class Outer{ public class Inner{ } }
内部类的访问特点:
- 内部类可以直接访问外部类的成员,包括私有
- 外部类要访问内部类的成员,必须创建对象
2.内部类对象的创建及访问(公共内部类)
//创建一个包含内部类的类 public class Animal { public int age = 10; //定义内部类 public class wyk{ public void show(){ System.out.println(age); } } } //执行类 public class Test { public static void main(String[] args) { //创建内部类对象 Animal.wyk w = new Animal().new wyk(); w.show(); } }
3.内部类对象的创建及访问(私有修饰内部类)
//创建一个包含private内部类的类 public class Animal { public int age = 10; public class wyk{ public void show(){ System.out.println(age); } } public void method(){ wyk w = new wyk(); w.show(); } } //测试类 public class Test { public static void main(String[] args) { Animal a = new Animal(); a.method();//10 } }
4.匿名内部类(开发中的实际应用)
//接口 public interface Jumpping { void jump(); } //接口操作类 public class JumppingOperator { public void method(Jumpping j){ j.jump(); } } //执行类 public class JumppingDemo { public static void main(String[] args) { //需求:创建接口操作类的对象,调用method方法 JumppingOperator j = new JumppingOperator(); j.method(new Jumpping() { @Override public void jump() { System.out.println("可以调高了"); } }); } }
常规写法
public class Main { public static void main(String[] args) { new Main(){ void sound(){ System.out.println("匿名内部类"); } }.sound(); } }
15.Math类方法
public class Test { public static void main(String[] args) { //public static int abs (int a):返回参数的绝对值 System.out.println(Math.abs(88)); System.out.println(Math.abs(-88)); System.out.println("----------"); //public static double ceil (double a):返回大于或等于参数的最小double值,等于一个整数 System.out.println(Math.ceil(12.34)); System.out.println(Math.ceil(12.56)); System.out.println("----------"); //public static double floor (double a):返回小于或等于参数的最大double值,等于一个证书 System.out.println(Math.floor(12.34)); System.out.println(Math.floor(12.56)); System.out.println("----------"); //public static int round(float a):按照四舍五入返回最接近参数的int System.out.println(12.34F); System.out.println(12.56F); System.out.println("----------"); //public static int max(int a,int b):返回两个int值中的较大值 System.out.println(Math.max(66,88)); System.out.println("----------"); //public static int min(int a,int b):返回两个int值中的较小值 System.out.println(Math.min(3,4)); System.out.println("----------"); //public static double pow(double a, double b):返回a的b次幂的值 System.out.println(Math.pow(2.0,3.0)); System.out.println("----------"); //public static double random():返回值为double的正值 [0.0,1.0) System.out.println(Math.random()); } }
16.System类方法
public class Test { public static void main(String[] args) { long start = System.currentTimeMillis();//返回当前系统时间(以毫秒值为单位) for(int i=0; i<10000; i++){ System.out.println(i); } long end = System.currentTimeMillis();//返回当前系统时间 System.out.println("共耗时"+(end-start)+"毫秒"); System.exit(0);//终止jvm虚拟机 } }
17.Object类
- Ovject是类层次结构的根,每个类都可以将Object作为超类,所有类都可以直接或者间接的继承自该类。
- 疑问:回想面向对象中,为什么子类的构造方法默认访问的是父类的无参构造方法呢?
- 因为他们的顶级父类(Object)只有无参构造方法
18.异常
1.异常简介
如果程序出现了问题,我们需要自己来处理,有两种方案
- try … catch …
try{ 可能出现异常的代码; } catch(异常类名 变量名){ 异常的处理代码; } /* 流程: 1. 程序从try里面开始执行 2. 出现异常,会自动生成一个异常类对象,该异常对象将被提交给Java运行时系统 3. 当Java运行时系统接收到异常对象时,会到catch中去找匹配的异常类,找到后进行异常的处理 4. 执行完毕之后,程序还可以继续往下执行。 */
2.Throwable的成员方法
方法名 说明 public String getMessage() 返回此 throwable 的详细消息字符串 public String toString() 返回此可抛出的简短描述 public void printStackTrace() 把异常的错误信息输出在控制台
3.throws和throw的区别
throws throw 用在方法声明之后,跟的是异常类名 用在方法体内,跟的是异常对象名 表示抛出异常,由该方法的调用者来处理 表示抛出异常,由方法体内的语句处理 表示出现异常的一种可能性,并不一定会发生这些异常 执行 throw 一定抛出了某种异常
19.冒泡排序
public class Test { public static void main(String[] args) { int[] arr = {163,130,81,168,171,192,12,32,425,11}; int temp; for(int i=1;i<arr.length;i++){ for (int j=0;j<i;j++){ if(arr[j]>arr[i]){ temp = arr[j]; arr[j] = arr[i]; arr[i] = temp; } } } for(int i:arr){ System.out.print(i+" ");//11 12 32 81 130 163 168 171 192 425 } } }
20.包装类
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
public class Test { public static void main(String[] args) { //创建包装类对象 Integer i = Integer.valueOf(123); System.out.println(i);//123 //String 转成 int String s = "100"; int a = Integer.parseInt(s); System.out.println(a);//100 //int 转成 String Integer i = Integer.valueOf(123); String s2 = String.valueOf(i); System.out.println(s2);//123 } }
1. 自动装箱和拆箱
装箱:把基本数据类型转换为对应的包装类类型
拆箱:把包装类类型转换为对应的基本数据类型
Integer i = 100; //自动装箱 i += 200; //i = i +200; i+200 自动拆箱; i = i + 200; 是自动装箱
- 注意:在使用包装类类型的时候,如果做操作,最好先判断是否为null,只要是对象,在使用前就必须进行不为null的判断
21.时间日期类的常用方法
1.Date 类、SimpleDateFormat 类
方法名 说明 public long getTime() 获取的是日期对象从1970年1月1日到现在的毫秒值 public void setTime(long time) 设定时间,给的是毫秒值 //方法类 import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class DateUtils { private DateUtils(){} /* 把日期转换成指定格式的字符串 */ public static String dateToString(Date date, String format){ SimpleDateFormat sdf = new SimpleDateFormat(format); String s = sdf.format(date); return s; } /* 把字符串解析为指定格式的日期 返回值类型:Date */ public static Date stringToDate(String s,String format) throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat(format); Date d = sdf.parse(s); return d; } } //执行类 import java.util.Date; public class Test { public static void main(String[] args){ Date d = new Date(); String s1 = DateUtils.dateToString(d,"yyyy年HH月dd日 HH:mm:ss"); String s2 = DateUtils.dateToString(d, "HH:mm:ss"); System.out.println("年月日时分秒:"+s1);//年月日时分秒:2021年17月13日 17:39:44 System.out.println("只有时分秒:"+s2);//只有时分秒:17:39:44 String str1 = "yyyy-HH-dd HH:mm:ss"; String str2 = "HH:mm:ss"; String s3 = DateUtils.dateToString(d, str1); String s4 = DateUtils.dateToString(d, str2); System.out.println(s3);//2021-17-13 17:39:44 System.out.println(s4);//17:39:44 } }
2. Calendar 日历类
常用方法:
方法名 说明 public final void set(int year,int month,int date) 设置当前日历的年月日 public int get(int field) 返回给定日历字段的值 public abstract void add(int field, int amount) 根据日历的规则,将指定的时间量添加或减去 import java.util.Calendar; public class Test { public static void main(String[] args){ //获取对象 Calendar c = Calendar.getInstance();//多态的形式 //public int get(int field) int year = c.get(Calendar.YEAR); int month = c.get(Calendar.MONTH)+1;//月份从0开始,需要加1 int date = c.get(Calendar.DATE); System.out.println(year+"年"+month+"月"+date+"日");//2021年11月14日 //3年前的今天 c.add(Calendar.YEAR,-3); int year2 = c.get(Calendar.YEAR); System.out.println("3年前:"+year2+"年");//3年前:2018年 c.set(2048,11,11); int year3 = c.get(Calendar.YEAR); int month3 = c.get(Calendar.MONTH); int date3 = c.get(Calendar.DATE); System.out.println("指定的日期:"+year3+"年"+month3+"月"+date3+"日");//指定的日期:2048年11月11日 } }import java.util.Calendar; public class Test { public static void main(String[] args){ //获取对象 Calendar c = Calendar.getInstance();//多态的形式 //public int get(int field) int year = c.get(Calendar.YEAR); int month = c.get(Calendar.MONTH); int date = c.get(Calendar.DATE); System.out.println(year+"年"+month+"月"+date+"日");//2021年10月14日 } }
22.集合
1.集合概述图
2.Collection 集合概述和使用
- 是单例集合的顶层接口,他表示一组对象,这些对象也被称为Collection的元素
- JDK不提供此接口的任何直接实现,他提供更具体的子接口,例如:Set和List
创建Collection集合的对象
- 多态的方式
- 具体的实现类ArrayList
import java.util.ArrayList; import java.util.Collection; public class DateUtils { public static void main(String[] args) { //创建Collection集合的对象 Collection<String> c = new ArrayList<>(); //添加元素:boolean add(E e) c.add("hello"); c.add("world"); c.add("java"); //输出集合 System.out.println(c); } }
Collection集合常用方法
方法名 说明 boolean add(E e) 添加元素 boolean remove(Object o) 从集合中移除指定的元素 void clear() 清空集合中的元素 boolean contains(Object o) 判断集合中是否存在指定的元素 boolean isEmpty() 判断集合是否为空 int size() 集合的长度,也就是集合中元素的个数 public class DateUtils { public static void main(String[] args) { //创建Collection集合的对象 Collection<String> c = new ArrayList<>(); //添加元素:boolean add(E e) c.add("hello"); c.add("world"); c.add("java"); //输出集合: System.out.println(c);//[hello, world, java] //移除指定元素:boolean remove(Object o) c.remove("hello"); System.out.println(c);//[world, java] //清空集合中的元素:void clear() c.clear(); System.out.println(c);//[] //判断集合中是否存在指定的元素:boolean contains(Object o) boolean aa = c.contains("java"); System.out.println(aa);//false //判断集合是否为空:boolean isEmpty() boolean empty = c.isEmpty(); System.out.println(empty);//true //集合的长度,也就是集合中元素的个数:int size() System.out.println(c.size());//0 } }
3.集合的遍历–迭代器
//创建集合添加元素遍历
public class DateUtils {
public static void main(String[] args) {
//创建Collection集合的对象
Collection<String> c = new ArrayList<>();
//添加元素:boolean add(E e)
c.add("hello");
c.add("world");
c.add("java");
//创建迭代器
Iterator<String> it = c.iterator();
while(it.hasNext()){
String next = it.next();
System.out.print(next+" ");//hello world java
}
}
}
//存储学生对象并遍历
public class Test {
public static void main(String[] args){
Student st1 = new Student("学生1",22);
Student st2 = new Student("学生2",23);
Student st3 = new Student("学生3",24);
//创建Collection集合的对象
Collection<Student> c = new ArrayList<>();
c.add(st1);
c.add(st2);
c.add(st3);
Iterator<Student> it1 = c.iterator();
while(it1.hasNext()){
Student next = it1.next();
System.out.println("学生姓名:"+next.getName()+"学生年龄:"+next.getAge());
}
}
}
---控制台输出---
学生姓名:学生1学生年龄:22
学生姓名:学生2学生年龄:23
学生姓名:学生3学生年龄:24
4.List集合
1.List集合概述
- 有序集合(也称为序列),用户可以精确控制列表中每个元素的插入位置,用户可以通过整数索引访问元素,并搜索列表中的元素
- 与Set集合不同,列表通常允许重复的元素
List集合特点
- 有序: 存储和取出的元素顺序一致
- 可重复:存储的元素可以重复
public class Test { public static void main(String[] args){ //创建集合对象 List<String> list = new ArrayList<>(); //添加元素 list.add("hello"); list.add("world"); list.add("java"); //添加重复的元素 list.add("world"); //迭代器遍历 Iterator<String> it = list.iterator(); System.out.print("List集合(有序,可重复): "); while(it.hasNext()){ String next = it.next(); System.out.print(next+" ");//List集合(有序,可重复): hello world java world } } }
2.List集合的特有方法
方法名 说明 void add(int index, E element) 在此集合中的指定位置插入指定的元素 E remove(int index) 删除指定索引处的元素,返回被删除的元素 E set(int index, E element) 修改指定索引处的元素,返回被修改的元素 E get(int index) 返回指定索引处的元素 public class Test { public static void main(String[] args){ //创建集合对象 List<String> list = new ArrayList<>(); //添加元素 list.add("hello"); list.add("world"); list.add("java"); //指定位置添加元素 list.add(1,"javase"); //下面这个会出现索引越界异常 IndexOutOfBoundsException,因为没有11这个索引位置 //list.add(11,"javase"); //E remove(int index):删除指定索引处的元素,返回被修改的元素 System.out.println(list.remove(1));//返回字符串 javase //E set(int index, E element): 修改指定索引处的元素,返回被修改的元素 System.out.println(list.set(1,"one"));//返回字符串 javase //E get(int index):返回指定索引处的元素 System.out.println(list.get(1));//one //输出集合所有元素 System.out.println(list);//[hello, one, java] //遍历集合 for(String str:list){ System.out.println(str); /* 控制台输出: hello one java */ } } }
3.List列表的并发修改异常
import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.ListIterator; public class Test { public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("hello"); list.add("world"); list.add("java"); /* 这里的循环遍历会报并发修改异常:ConcurrentModificationException 产生原因:迭代器遍历的过程中,通过集合对象修改了集合中元素的长度,造成了迭代器获取元素中判断预期修改值和实际修改值不一致 解决方案:使用for循环遍历,然后用集合对象做对应的操作即可 while(iterator.hasNext()){ String ss = iterator.next(); if (ss.equals("world")){ list.add("javaee"); } } */ //解决方案一:通过for循环解决解决 Iterator<String> iterator = list.iterator(); for (int i = 0; i < list.size(); i++) { String str = list.get(i); if (str.equals("world")) { list.add("javaee"); } } System.out.println(list);//[hello, world, java, javaee] //解决方案二:通过列表迭代器(ListLterator) ListIterator<String> iterator2 = list.listIterator(); while (iterator2.hasNext()) { String ss = iterator2.next(); if (ss.equals("world")) { iterator2.add("javaee"); } } System.out.println(list);//[hello, world, javaee, java, javaee] } }
4.ListLterator列表迭代器
(例子在👆上面的解决方案二)
ListLterator:列表迭代器
- 通过List集合的listlterator()方法得到,所以说它是List集合特有的迭代器
- 用于允许程序员沿任一方向遍历列表的列表迭代器,在迭代期间修改列表,并获取列表中迭代的当前位置
ListLterator中的常用方法
方法 说明 E next() 返回迭代中的下一个元素 boolean hasNext() 如果迭代具有更多元素,则返回true E previous() 返回列表中的上一个元素 boolean hasPrevious() 如果此列表迭代器在相反方向遍历列表时具有更多元素,则返回true void add(E e) 将指定的元素插入列表
5.List集合子类特点(ArrayList and LinkedList)
List集合常用子类:ArrayList,LinkedList,这两个子类都可以使用父类(List)的方法
- ArrayList:底层数据结构是数组,查询快,增删慢
- LinkedList:底层数据结构是链表,查询慢,增删快
LinkedList类的特有功能
方法名 说明 public void addFirst(E e) 在该列表开头插入指定的元素 public void addLast(E e) 将指定的元素追加到此列表的末尾 public E getFirst() 返回此列表中的第一个元素 public E getLast() 返回此列表中的最后一个元素 public E removeFirst() 从此列表中删除并返回第一个元素 public E removeLast() 从此列表中删除并返回最后一个元素 import java.util.LinkedList; public class Test { public static void main(String[] args) { LinkedList<String> list = new LinkedList<>(); list.add("java"); list.add("hello"); list.add("world"); System.out.println(list);//[java, hello, world] list.addFirst("first"); System.out.println(list);//[first, java, hello, world] list.addLast("end"); System.out.println(list);//[first, java, hello, world, end] String str1 = list.getFirst(); System.out.println(str1);//first String str2 = list.getLast(); System.out.println(str2);//end list.removeFirst(); System.out.println(list);//[java, hello, world, end] list.removeLast(); System.out.println(list);//[java, hello, world] } }
6.Set集合
1. Set集合特点:
Set包含 HashSet和TreeSet
- 不包含重复元素的集合
- 没有带索引的方法,所以不能使用普通for循环遍历
/* HashSet:对集合的迭代顺序不做任何保证 */ public class Test { public static void main(String[] args) { Set<String> set = new HashSet<String>(); //添加元素 set.add("hello"); set.add("world"); set.add("java"); //不包含重复元素 set.add("java"); for(String str : set){ System.out.print(str+" ");//world java hello } } }
2. 哈希(Hash)值
哈希值:是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
Object类中有一个方法可以获取对象的哈希值
- public int hashCode(): 返回对象的哈希码值
对象的哈希值特点
- 同一个对象多次调用hashCode()方法返回的哈希值是相同的
- 默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同
public class Test { public static void main(String[] args) { Student st1 = new Student(); Student st2 = new Student("林青霞", 22); //同一个对象多次调用hashcode() 方法返回值哈希值是相同的 这是调用object的hashcode System.out.println(st1.hashCode());//460141958 System.out.println(st1.hashCode());//460141958 //默认情况下 如果在student类中对object的hashcode重写 那么值就成了重写方法体的内容了 System.out.println(st2.hashCode());//1163157884 System.out.println("=========="); System.out.println("hello".hashCode());//99162322 System.out.println("world".hashCode());//113318802 //字符串调用hashcode() 一样 是因为String重写了object的方法 System.out.println("java".hashCode());//3254818 System.out.println("java".hashCode());//3254818 System.out.println("重地".hashCode());//1179395 System.out.println("童话".hashCode());//1011096 } }
3.HashSet集合概述和特点
HashSet集合特点
- 底层数据结构是哈希表
- 对集合的迭代顺序不做任何保证,也就是说不保证存储和取出的元素顺序一致
- 没有带索引的方法,所以不能使用普通for循环遍历
- 由于是Set集合,所以是不包含重复元素的集合
public class Test { public static void main(String[] args) { HashSet<String> hs = new HashSet<>(); hs.add("你好"); hs.add("java"); hs.add("world"); hs.add("world"); for(String i:hs){ //元素输出不一致 System.out.print(i+" ");//java world 你好 } } }
HashSet要保证元素唯一性,需要重写hashCode()和equals() 方法,如下👇
//学生实体类 public class Student { String name; int age; public Student(String name, int age) { this.name = name; this.age = age; } public Student() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; if (age != student.age) return false; return name != null ? name.equals(student.name) : student.name == null; } @Override public int hashCode() { int result = name != null ? name.hashCode() : 0; result = 31 * result + age; return result; } } //测试类 import java.util.HashSet; /* 如果在Student实体类中没有重写equals()和hashCode()方法,那么下面的测试类 将会在控制台输出如下值:(可以看到有重复的对象值出现,学生3),这里的输出顺序不是倒序 纯属是巧合,因为hashSet存储和取出顺序是不能保证一致性的 学生3,25 学生2,23 学生1,19 学生3,25 */ public class Test { public static void main(String[] args) { HashSet<Student> hs = new HashSet<>(); Student st1 = new Student("学生1",19); Student st2 = new Student("学生2",23); Student st3 = new Student("学生3",25); //创建重复元素对象 Student st4 = new Student("学生3",25); hs.add(st1); hs.add(st2); hs.add(st3); //添加重复元素对象 hs.add(st4); for(Student i:hs){ System.out.println(i.getName()+","+i.getAge()); } } } -----控制台输出----- 学生1,19 学生3,25 学生2,23
4.LinkedHashSet集合
LinkedHashSet集合特点
- 哈希表和链表实现的Set接口,具有可预测的迭代次序
- 由链表保证元素有序,也就是说元素的存储和取出的顺序是一致的
- 由哈希表保证元素唯一,也就是说没有重复的元素
import java.util.LinkedHashSet; public class Test { public static void main(String[] args) { LinkedHashSet<String> list = new LinkedHashSet<>(); list.add("你好"); list.add("java"); list.add("world"); //插入重复的元素 list.add("world"); for(String str:list){ System.out.print(str+" ");//你好 java world } } }
5.TreeSet集合概述和特点
元素有序,这里的顺序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体排序方式取决于构造方法
- TreeSet(): 根据其元素的自然排序进行排序
- TreeSet(Comparator comparator): 根据指定的比较器进行排序
没有带索引的方法,所以不能使用普通for循环遍历
由于是Set集合,所以不包含重复元素的集合
import java.util.TreeSet; public class Test { public static void main(String[] args) { //创建集合对象 TreeSet<Integer> ts = new TreeSet<>(); ts.add(10); ts.add(40); ts.add(30); ts.add(50); ts.add(20); //添加重复元素 添加不进去,因为TreeSet存储的数据由唯一性 ts.add(30); for(Integer i:ts){ System.out.println(i); } } } -----控制台输出-----(此输出按照自然排序输出,与添加时候的顺序无关,并且没有重复值) 10 20 30 40 50
1.自然排序Comparable
/* + 存储学生对象并遍历,创建TreeSet集合使用无参构造方法 + 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母排序 */ public class Student implements Comparable<Student>{ String name; int age; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } /* 此方法根据返回值正数(升序),负数(降序),零(相同元素,只存第一个相同的元素) 排序不同 */ @Override public int compareTo(Student st) { // return 0;//零那么会认为是相同的元素,只会存储相同中第一个元素 // return 1;//升序排列 // return -1;//降序排列 /* this.age 是执行类中的st2的年龄 st.age 是执行类中st1的年龄 如果 this.age - st.age>0 那就升序排列 否则降序排列 按照年龄从小到大排序👇 */ int num = this.age - st.age; // return num; /* 年龄相同时,按照姓名的字母顺序排序 如果num==0 为true 那就返回this.name.compareTo(st.name)的值 如果num==0 为false 那就返回num的值 */ return num==0?this.name.compareTo(st.name):num; } } //执行类 import java.util.TreeSet; public class Test { public static void main(String[] args) { //创建集合对象 TreeSet<Student> ts = new TreeSet<>(); Student st1 = new Student("学生1",22); Student st2 = new Student("学生2",22); Student st3 = new Student("学生3",24); Student st4 = new Student("学生4",25); Student st5 = new Student("学生5",26); ts.add(st1); ts.add(st2); ts.add(st3); ts.add(st4); ts.add(st5); for(Student st:ts){ System.out.println(st.getName()); System.out.println(st.getAge()); } } }
2。TreeSet不同随机数
/*
目标:完成1-20之内不同的十个随机数存储到集合
*/
public class Test2 {
public static void main(String[] args) {
Set<Integer> set = new TreeSet<Integer>();//
Random r = new Random();
while(set.size()<10){
set.add(r.nextInt(20)+1);
}
System.out.println(set);//[1, 2, 3, 4, 9, 11, 13, 14, 16, 20]
}
}
23.泛型
泛型:是JDK5引入的特性,它提供了编译时类型安全检测机制,该机制允许在编译时检测到非法的类型,简而言之就是将类型由原来的具体的类型参数化,然后在使用/调用时传入具体的类型。这种参数类型可以用在类、方法、接口中,分别被称为泛型类、泛型方法、泛型接口。
泛型定义格式:
- <类型>:指定一种类型的格式。这里的类型可以看成是形参
- <类型1,类型2…>:指定多种类型的格式,多种类型之间用逗号隔开。这里的类型可以看成是形参
- 将来具体调用时候给定的类型可以看成是实参,并且实参的类型只能是引用数据类型.
泛型的好处
- 把运行时期的问题提前到了编译期间
- 避免了强制类型转换
1.泛型类
//泛型类 public class Generic<T>{ private T t; public T getT() { return t; } public void setT(T t) { this.t = t; } } //测试类 public class Test { public static void main(String[] args) { Generic<String> g1 = new Generic<String>(); g1.setT("林青霞"); System.out.println(g1.getT());//林青霞 Generic<Integer> g2 = new Generic<Integer>(); g2.setT(30); System.out.println(g2.getT());//30 } }
2.泛型方法
泛型方法的定义格式
public <T> void show(T t){}
//泛型方法 public class Generic<T>{ public <T> void getT2(T str){ System.out.println(str); } } //测试类 public class Test { public static void main(String[] args) { Generic<Integer> g2 = new Generic<Integer>(); g2.getT2("你好");//你好 g2.getT2(123);//123 } }
3.泛型接口
//接口
public interface Generice<T> {
void show(T t);
}
//接口实现类
public class GenericImpl<T> implements Generice<T>{
@Override
public void show(T t) {
System.out.println(t);
}
}
//测试类
public class Test {
public static void main(String[] args) {
Generice<String> g1 = new GenericImpl<String>();
g1.show("林青霞");//林青霞
Generice<Integer> g2 = new GenericImpl<Integer>();
g2.show(30);//30
}
}
4.类型通配符
为了表示各种泛型List的父类,可以使用类型通配符
- 类型通配符:<?>
- List<?>:表示元素类型位置的List,它的元素可以匹配任何的类型
- 这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素添加到其中
如果说我们不希望List<?>是任何泛型List的父类,只希望它代表某一类泛型List的父类,可以使用类型通配符的上限
- 类型通配符上限:<?extends 类型>
- List<? extends Number>:他表示的类型是 Number或者其子类型
除了可以指定类型通配符的上限,我们也可以指定类型通配符的下限
- 类型通配符下限:<? super 类型>
- List<? super Number>:他表示的类型是 Number或者其父类型
import java.util.ArrayList; import java.util.List; public class Test { public static void main(String[] args) { //类型通配符:<?> List<?> list1 = new ArrayList<Object>(); List<?> list2 = new ArrayList<Object>(); List<?> list3 = new ArrayList<Object>(); System.out.println("--------"); //类型通配符的上限:<? extends 类型> List<? extends Number> list5 = new ArrayList<Number>(); List<? extends Number> list6 = new ArrayList<Integer>(); //类型通配符的下限:<? super 类型> List<? super Number> list7 = new ArrayList<Object>(); List<? super Number> list8 = new ArrayList<Number>(); //List<? super Number> list9 = new ArrayList<Integer>();//这里Integer比Number低,报错了 } }
5.可变参数
可变参数又称参数个数可变,用作方法的形参出现,那么方法参数个数就是可变的了
- 格式:修饰符 返回值类型 方法名(数据类型… 变量名){}
- 范例:public static int sum(int… a){}
可变参数注意事项
- 这里的变量其实就是一个数组
- 如果一个方法有多个参数,包含可变参数,可变参数要放在最后
public class Test { public static void main(String[] args) { System.out.println(sum(10)); System.out.println(sum(10,20)); System.out.println(sum(10,20,30)); System.out.println(sum(10,20,30,40)); System.out.println(sum(10,20,30,40,50)); } public static int sum(int... a){ /* * 注意: 这里的变量其实是一个数组。 * 如果一个方法有多个参数,包含可变参数,可变参数要放在最后。 * */ int sum = 0; for(int i : a){ sum += i; } return sum; } }
257-279
24.IO流
1.IO流概述和分类
IO流概述:
- IO:输入/输出(input/Output)
- 流:是一种抽象概念,是对数据传输的总称。也就是说数据在设备间的传输称为流,流的本质是数据传输
- IO流就是用来处理设备间数据传输问题的
- 常见的应用:文件复制、文件上传、文件下载
IO流的分类:
- 按照数据的流量划分
- 输入流:读数据
- 输出流:写数据
- 按照数据类型划分
- 字节流
- 字节输入流、字节输出流
- 字符流
- 字符输入流,字符输出流
一般来说,我们说IO流的分类是按照数据类型来分的,两种流如何都在什么情况下使用呢?
- 如果右键记事本打开能读懂,用字符流
- 读不懂,用字节流,不知道用哪种,就用字节流(万能的流)
2.字节流写数据
字节流抽象基类
- InputStream:这个抽象类是字节输入流的所有类的超类
- OutputStream:这个抽象类是表示字节输出流的所有类的超类
- 子类名特点:子类名称都是以其父类名作为子类名的后缀
FileOutputStream:文件输出流用于将数据写入文件
- FileOutputStream(String name):创建文件输出流以指定的名称写入文件
import java.io.FileOutputStream; import java.io.IOException; /* 使用字节输出流写数据的步骤 + 创建字节输出流对象(调用系统功能创建了对象,创建字节输出流对象,让字节输出流对象指向文件) + 调用字节输出流对象的写数据方法 + 释放资源(关闭此文件输出流并释放与此流相关联的任何系统资源) */ public class Test { public static void main(String[] args) throws IOException { //创建字节输出流对象 FileOutputStream fos = new FileOutputStream("E:\\StudyProject\\IdeaJavaProject\\TestOne\\1.txt"); /* * 做了三件事情 * A:调用系统功能创建了文件 * B:创建了字节输出流对象 * C:让字节输出流对象 * */ //void write (int b): 将指定的字节写入此文件输出流 fos.write(97); fos.write(57); fos.write(55); fos.write(58); //文本内容:a97: //最后都要释放资源 fos.close(); } }
字节流写数据的三种方式
方法名 说明 void write(int b) 将指定的字节写入此文件输出流,一次写一个字节数据 void write(byte[] b) 将 b.length 字节从指定的字节数组写入此文件输出流,一次写一个字节数组数据 void write(byte[] b, int off, int len) 将 len 字节从指定的字节数组开始,从偏移量 off 开始写入此文件输出流,一次写一个字节数组的部分数据
25.多线程
1.1进程
进程:正在运行的程序
- 是系统进行资源分配和调用的独立单位
- 每一个进程都有它自己的内存空间和系统资源
1.2线程
线程:是进程中的单个顺序控制流,是一条执行路径
- 单线程:一个进程如果只有一条执行路径,则称为单线程程序
- 多线程:一个进程如果有多条执行路径,则称为多线程程序
1.3多线程的实现方式
方式1:继承Thread类
- 定义一个类 MyThread 继承 Thread 类
- 在 MyThread 类中重写 run() 类
- 创建 MyThread 类的对象
- 启动线程
//继承Thread类 public class MyRunnable extends Thread{ @Override public void run() { for(int i=0;i<100;i++){ System.out.println(MyRunnable.currentThread().getName()+":"+i); } } } //执行类 public class MyThreadDemo { public static void main(String[] args) { MyRunnable m1 = new MyRunnable(); MyRunnable m2 = new MyRunnable(); m1.setName("高铁"); m2.setName("飞机"); m1.start(); m2.start(); } }
两个小问题:
- 为什么要重写run()方法?
- 因为 run() 是用来封装被线程执行的代码
- run() 方法和 start() 方法的区别?
- run():封装线程执行的代码,直接调用,相当于普通方法的调用
- start():启动线程;然后由JVM调用此线程的run()方法
方式2:实现Rannable接口
- 定义一个类MyRunnable实现Rannable接口
- 在MyRunnable类中重写run()方法
- 创建MyRunnable类的对象
- 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
- 启动线程
//实现接口类 public class MyRunnable implements Runnable{ @Override public void run() { for(int i=0;i<100;i++){ System.out.println(Thread.currentThread().getName()+":"+i); } } } //执行类 public class MyThreadDemo { public static void main(String[] args) { //创建MyRunnable类的对象 MyRunnable m1 = new MyRunnable(); //创建Thread类的对象,把MyRunnable对象作为构造方法的参数 Thread t1 = new Thread(m1,"高铁"); Thread t2 = new Thread(m1,"飞机"); //启动线程 t1.start(); t2.start(); } }
1.4设置和获取线程名称
Thread 类中设置和获取线程名称的方法
- void setName(String name):将此线程的名称更改为等于参数 name
- String getName():返回此线程的名称
- 通过构造方法也可以设置线程名称
如何获取 main() 方法所在的线程名称?
- public static Thread currentThread():返回对当前正在执行的线程对象的引用
1.5线程调度
线程有两种调度模型
- 分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
- 抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些(Java使用的是抢占式调度模型)
假如计算机只有一个CPU,那么CPU在某一个时刻智能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的
Thread类中设置和获取线程优先级的方法
public final int getPriority():返回此线程的优先级
public final void setPriority(int newPriority):更改此线程的优先级
线程默认优先级是5;线程优先级的范围是:1-10
线程优先级高仅仅表示线程获取的CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到你想要的效果
1.6线程控制
方法名 | 说明 |
---|---|
static void sleep(long millis) | 使当前正在执行的线程停留(暂时执行)指定的毫秒数 |
void join() | 等待这个线程死亡 |
void setDaemon(boolean on) | 将此线程标记为守护线程,当运行的县城都是守护线程时,Java虚拟机将退出 |
2.线程同步
需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
思路:
- 定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;
- 在SellTicket类中重写run()方法实现买票,代码步骤如下
- A:判断票数大于0,就卖票,并告知是哪个窗口卖的
- B:卖了票,总票数要减1
- C:票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行
- 定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下
- A:创建SellTicket类的对象
- B:创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
- C:启动线程