参考(黑马):https://www.bilibili.com/video/BV1Cv411372m/
java语法
idea快捷键
数据类型
- 数据类型分为几种?
- 基本数据类型:4大类8种
整型:byte short int(默认) long
浮点:float double(默认)
字符:char
布尔:boolean - 引用数据类型
数组、类、接口
- 随便写的整数、小数字面量,他们默认什么类型?
- 23,默认是int类型,加上L/l就是long类型的数据了
- 23.8,默认是double类型,加上F/f就是float类型了
类型转换
自动类型转换
表达式的自动类型转换
在表达式中,小范围类型的变量,会自动转换成表达式中较大范围的类型,再参与运算。
注意事项
- 表达式的最终结果类型由表达式中的最高类型决定。
- 在表达式中,byte、short、char是直接转换成int类型参与运算的。
强制类型转换
- 什么是强制类型转换?
- 默认情况下,大范围类型的变量直接赋值给小范围类型的变量会报错!
- 可以强行将类型范围大的变量、数据赋值给类型范围小的变量。
- 强制类型转换有哪些需要注意的?
- 可能出现数据丢失。
- 小数强制转换成整数是直接截断小数保留整数。
switch
数组
静态数组
int c[] = {1,2,3};//声明并创建内存空间,直接赋值
int[] c1 = {1,2,3};//声明并创建内存空间,直接赋值
动态数组
int a[] = new int[5];//创建长度为5的一维数组等待赋值,初始值为0
int[] a1 = new int[5];//创建长度为5的一维数组等待赋值,初始值为0
int b[] = new int[] {1,2,3};//声明并创建内存空间且赋值
int[] b1 = new int[] {1,2,3};//声明并创建内存空间且赋值
参数传递
基本类型和引用类型的参数在传递的时候有什么不同?
- 都是值传递。
- 基本类型的参数传输存储的数据值。
- 引用类型的参数传输存储的地址值。
方法重载
- 什么是方法重载?
一个类中,多个方法的名称相同,但它们形参列表不同。 - 方法重载需要注意什么?
- 一个类中,只要一些方法的名称相同、形参列表不同,那么它们就是方法重载了,其它的都不管(如:修饰符,返回值类型是否一样都无所谓)。
- 形参列表不同指的是:形参的个数、类型、顺序不同,不关心形参的名称。
- 方法重载有啥应用场景?
开发中我们经常需要为处理一类业务,提供多种解决方案,此时用方法重载来设计是很专业的
构造器
-
构造器长什么样子?
-
构造器在哪里调用,我们常用它来干嘛?
- 对象创建时,我们可以指定对象去调用哪个构造器执行。
- 构造器常用于完成对象初始化(常见的应用场景是完成对象的成员变量的初始化赋
- 构造器在使用时,有哪2个注意事项?
- 类在设计时,如果不写构造器,Java会为类自动生成一个无参构造器的
- 一旦定义了有参构造器,Java就不会帮我们的类生成无参构造器了,此时就建议自己手写一个无参构造器出来了。
实体类
- 什么是实体类? 有啥特点?
- 成员变量必须私有,且要为他们提供get、set方法;
- 必须有无参数构造器
- 仅仅只是一个用来保存数据的java类,可以用它创建对象,保存某个事物的数据。
- 实体类有啥应用场景?
实体类对应的是软件开发里现在比较流行的开发方式,数据和数据的业务处理相分离
包的调用
注意事项
- 如果当前程序中,要调用自己所在包下的其他程序,可以直接调用。(同一个包下的类,互相可以直接调用)
- 如果当前程序中,要调用其他包下的程序,则必须在当前程序中导包,才可以访问!导包格式:import 包名.类名;
- 如果当前程序中,要调用Java提供的程序,也需要先导包才可以使用;但是java.lang包下的程序是不需要我们导包的,可以直接使用。
- 如果当前程序中,要调用多个不同包下的程序,而这些程序名正好一样,此时默认只能导入一个程序,另一个程序必须带包名访问。
static
static叫静态,可以修饰成员变量、成员方法。
成员变量按照有无static修饰,分为两种:
- 类变量:有static修饰,属于类,在计算机里只有一份,会被类的全部对象共享
- 实例变量(对象的变量)
使用类方法(有static)、实例方法(无static)时的几点注意事项
- 类方法中可以直接访问类成员,不可以直接访问实例成员。
- 实例方法中既可以直接访问类成员,也可以直接访问实例成员
- 实例方法中可以出现this关键字,类方法中不可以出现this关键字的
代码块
代码块是类的5大成分之一(成员变量、构造器、方法、代码块、内部类)。
代码块分为两种:
- 静态代码块:
格式:static{ }
特点:类加载时自动执行,由于类只会加载一次,所以静态代码块也只会执行一次。
作用:完成类的初始化,例如:对类变量的初始化赋值。
- 实例代码块:
格式:{ }
特点:每次创建对象时,执行实例代码块,并在构造器前执行。
作用:和构造器一样,都是用来完成对象的初始化的,例如:对实例变量进行初始化赋值
工具类
多学一招
final
- final关键字是最终的意思,可以修饰(类、方法、变量)
- 修饰类:该类被称为最终类,特点是不能被继承了
- 饰方法:该方法被称为最终方法,特点是不能被重写了
- 修饰变量:该变量只能被赋值一次。
final修饰变量的注意
- final修饰基本类型的变量,变量存储的数据不能被改变,
- final修饰引用类型的变量,变量存储的地址不能被改变,但地址所指向对象的内容是可以被改变的。
常量
- 使用了 static final修饰的成员变量就被称为常量;
- 作用:通常用于记录系统的配置信息。
使用常量记录系统配置信息的优势、执行原理
- 代码可读性更好,可维护性也更好。
- 程序编译后,常量会被“宏替换”:出现常量的地方全部会被替换成其记住的字面量这样可以保证使用常量和直接用字面量的性能是一样的。
权限修饰符
权限修饰符用来限制类中的成员(成员变量、成员方法、构造器、代码块…)能够被访问的范围。
权限修饰符有几种?各自的作用是什么?
继承
Java中提供了一个关键字extends。用这个关键字,可以让一个类和另一个类建立起父子关系。
- 继承的特点
子类能继承父类的非私有成员(成员变量、成员方法) - 继承后对象的创建
- 子类的对象是由子类、父类共同完成的
不能被继承的父类成员
- private成员
- 子类与父类不在同包,使用默认访问权限的成员
- 构造方法
方法重写
- 当子类觉得父类中的某个方法不好用,或者无法满足自己的需求时,子类可以重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写。
注意:
- 重写后,方法的访问,Java会遵循就近原则。
- 重写小技巧:使用Override注解,他可以指定java编译器,检查我们方法重写的格式是否正确,代码可读性也会更好。
- 写父类方法时,访问权限必须大于或者等于父类该方法的权限(public>protected>缺省)
- 重写的方法返回值类型,必须与被重写方法的返回值类型一样,或者范围更小。
- 私有方法、静态方法不能被重写,如果重写会报错的。
子类方法访问其他成员,依照就近原则
- 先子类局部范围找。
- 然后子类成员范围找。
- 然后父类成员范围找,如果父类范围还没有找到则报错。
super()
如果子父类中,出现了重名的成员,会优先使用子类的,如果此时一定要在子类中使用父类的怎么办?
可以通过super关键字,指定访问父类的成员:super.父类成员变量/父类成员方法
子类构造器
特点:
子类的全部构造器,都会先调用父类的构造器,再执行自己
子类构造器是如何实现调用父类构造器的?
- 默认情况下,子类全部构造器的第一行代码都是 super()(写不写都有),它会调用父类的无参数构造器。
- 如果父类没有无参数构造器,则我们必须在子类构造器的第一行手写super(…),指定去调用父类的有参数构造器。
this()调用兄弟构造器
任意类的构造器中,是可以通过this(…)去调用该类的其他构造器的。
this(…)和super(…)使用时的注意事项:
this(…)、super(…)都只能放在构造器的第一行,因此,有了this(…)就不能写super(…)了,反之亦然。
多态
-
什么是多态?
多态是在继承/实现情况下的一种现象,表现为:对象多态、行为多态。
-
多态的前提
- 有继承/实现关系;
- 存在父类引用子类对象;
- 存在方法重写
- 多态的注意事项
- 多态是对象、行为的多态,Java中的属性(成员变量)不谈多态。即不能使用多态访问子类成员变量
- 多态下不能使用子类的独有功能。
- 使用多态的好处
- 在多态形式下,右边对象是解耦合的,更便于扩展和维护。
- 定义方法时,使用父类类型的形参,可以接收一切子类对象,扩展性更强、更便利
多态的类型转换
- 自动类型转换:父类 变量名=new 子类();
例如:People p = new Teacher(); - 强制类型转换:子类 变量名=(子类)父类变量;
例如 Teacher t = (Teacher) p ;
强制类型转换的一个注意事项
- 存在继承/实现关系就可以在编译阶段进行强制类型转换,编译阶段不会报错
- 运行时,如果发现对象的真实类型与强转后的类型不同,就会报类型转换异常(ClassCastException)的错误出来。
强转前,Java建议:
- 使用instanceof关键字,判断当前对象的真实类型,再进行强转
抽象
- 在Java中有一个关键字叫:abstract ,它就是抽象的意思,可以用它修饰类、成员方法。
- abstract修饰类,这个类就是抽象类;修饰方法,这个方法就是抽象方法。
抽象类的注意事项、特点
- 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类。
- 类该有的成员(成员变量、方法、构造器)抽象类都可以有。
- 抽象类最主要的特点:抽象类不能创建对象,仅作为一种特殊的父类,让子类继承并实现。
- 父类知道每个子类都要做某个行为,但每个子类要做的情况不一样,父类就定义成抽象方法,交给子类去重写实现,我们设计这样的抽象类,就是为了更好的支持多态。
- 一个类继承抽象类,必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
接口
- Java提供了一个关键字interface,用这个关键字我们可以定义出一个特殊的结构:接口。
- 注意:接口不能创建对象;接口是用来被类实现(implements)的,实现接口的类称为实现类。
- 一个类可以实现多个接口(接口可以理解成干爹),实现类实现多个接口,必须重写完全部接口的全部抽象方法,否则实现类需要定义成抽象类。
接口的好处(重点)
- 弥补了类单继承的不足,一个类同时可以实现多个接口。
- 让程序可以面向接口编程,这样程序员就可以灵活方便的切换各种业务实现。
JDK8之后,接口的三种新增方法
接口的多继承
一个接口可以同时继承多个接口
接口多继承的作用:便于实现类去实现。
接口其他注意事项(了解)
- 一个接口继承多个接口,如果多个接口中存在方法签名冲突,则此时不支持多继承,
2.一个类实现多个接口,如果多个接口中存在方法签名冲突,则此时不支持多实现。- 一个类继承了父类,又同时实现了接口,父类中和接口中有同名的默认方法,实现类会优先用父类的。
- 一个类实现了多个接口,多个接口中存在同名的默认方法,可以不冲突,这个类重写该方法即可。
内部类
- 内部类是类中的五大成分之一(成员变量、方法、构造器、内部类、代码块),如果一个类定义在另一个类的内部,这个类就是内部类
- 场景:当一个类的内部,包含了一个完整的事物,且这个事物没有必要单独设计时,就可以把这个事物设计成内部类。
成员内部类
- 成员内部类就是类中的一个普通成员,类似前面我们学过的普通的成员变量、成员方法。
注意:IDK16之前,成员内部类中不能定义静态成员,IDK16开始也可以定义静态成员了创建对象的格式:
- 创建类对象
静态内部类
-
有static修饰的内部类,属于外部类自己持有。
-
创建对象的格式:
-
静态内部类中访问外部类成员的特点(类似静态方法)
可以直接访问外部类的静态成员,不可以直接访问外部类的实例成员。
匿名内部类(匿名类)
-
就是一种特殊的局部内部类。所谓匿名:指的是程序员不需要为这个类声明名字。
-
特点:匿名内部类本质就是一个子类,并会立即创建出一个子类对象。
-
作用:用于更方便的创建一个子类对象。
匿名内部类在开发中的使用场景
- 通常作为一个参数传输给方法。
枚举
- 枚举是一种特殊类。
- 枚举类的格式:
枚举类的特点:
- 枚举类的第一行只能罗列一些名称,这些名称都是常量,并且每个常量记住的都是枚举类的一个对象
- 枚举类的构造器都是私有的(写不写都只能是私有的),因此,举类对外不能创建对象。
- 枚举都是最终类,不可以被继承。
- 枚举类中,从第二行开始,可以定义类的其他各种成员,
- 编译器为枚举类新增了几个方法,并且枚举类都是继承:java.lang.Enum类的,从enum类也会继承到一些方法
使用枚举类实现单例设计模式
枚举的常见应用场景
- 用来表示一组信息,然后作为参数进行传输
与常量的对比
- 选择定义一个一个的常量来表示一组信息,并作为参数传输
好处:参数值不受约束。- 选择定义枚举表示一组信息,并作为参数传输
好处:代码可读性好,参数值得到了约束,对使用者更友好,建议使用!
泛型
- 定义:定义类、接口、方法时,同时声明一个或多个类型变量(如:<E>),称为泛型类、泛型接口、泛型方法,统称为泛型。
PS: <E>可以是类名 - 本质:把具体的数据类型作为参数传给类型变量
注意:
- 擦除问题:泛型是工作在编译阶段的,一旦程序编译成class文件,class文件中就不存在泛型了。
- ==泛型不支持基本数据类型,只是支持对象类型(引用数据类型)。
泛型类
- 声明泛型类后,即可在类中使用泛型变量E、T、K、V等代表一切类型。在创建对象时,指定泛型变量的具体类型,泛型类中的泛型变量便被自动替换为指定的具体类型
public static void main(String[] args){
A<Cat, String> a = new A<>();
B<Dog> b = new B<>();
}
PS: 泛型E可以规定继承关系
/*
泛型类
*/
public class A<E, T>{
public boolean add(E e, T t){
}
}
//E必须继承Animal类
public class B<E extends Animal>{
}
泛型接口
- 声明泛型接口后,即可在接口中使用泛型变量E、T、K、V等代表一切类型。在实现接口时需要指定泛型变量的具体类型;且实现类中,接口的实现方法也必须为具体类型。
public interface Data<T> {
void add(T t);
ArrayList<T> getByName(String name);
}
public class TeacherData implements Data<Teacher>{
@Override
public void add(Teacher teacher) {
}
@Override
public ArrayList<Teacher> getByName(String name) {
return null;
}
}
public class StudentData implements Data<Student>
{
@Override
public void add(Student student) {
}
@Override
public ArrayList<Student> getByName(String name) {
return null;
}
}
泛型方法
public class Test {
public static void main(String[] args) {
System.out.println(test("java"));
Dog dog = test(new Dog())); //也可以狗类型
}
//泛型方法
public static <T> T test(T t){
return t;
}
}
通配符“?”
在使用泛型时可以用“?”代表一切类型
public static void go_1(ArrayList<?> cars){
}
//集合中只能接收Car及其子类的引用数据类型。(上限)
public static void g0_2(ArrayList<? extend Car> cars){
}
//集合中只能接收Car及其夫类的引用数据类型。(下限)
public static void g0_2(ArrayList<? super Car> cars){
}
Lambda表达式
- JKD8新增语法,用于简化内部类代码写法。
- //只能简化函数式接口的匿名内部类
- 实例
public class LambdaDemo {
public static void main(String[] args) {
Animal a = new Animal(){
@Override
public void run() {
System.out.println("狗跑的得快!!");
}
};
a.run();
//只能简化函数时接口
Swimming s = () -> {
System.out.println("学生快乐游泳~~~~");
};
}
}
interface Swimming{
void swim();
}
abstract class Animal{
public abstract void run();
}
- 省略写法(进一步简化Lamda表达式)
- 参数类型可以省略不写。
- 如果只有一个参数,参数类型可以省略,同时()也可以省略。
- 如果Lambda表达式中的方法体代码只有一行代码,可以省略大括号不写,同时要省略分号!此时,如果这行代码是return语句,也必须去掉return不写。
方法引用
- JDK8新特性
- 进一步简化Lambda表达式
- 标志性符号:::
静态方法的引用
- 类名:: 静态方法
- 使用场景:如果某个Lambda表达式里只是调用一个静态方法,并且前后参数的形式一致,就可以使用静态方法引用。
实例方法的引用
- 对象名:: 实例方法
- 使用场景:如果某个Lambda表达式里只是调用一个实例方法,并且前后参数的形式一致,就可以使用实例方法引用。
特定类型方法的引用
- 类型:: 方法
- 使用场景:如果某个Lambda表达式里只是调用一个实例方法,并且前面参数列表中的第一个参数是作为方法的主调,后面的所有参数都是作为该实例方法的入参的,则此时就可以使用特定类型的方法引用。
构造器引用
正则表达式
- 作用一:用来校验数据格式是否合法
- 作用二:在一段文本中查找满足要求的内容
书写规则
异常
- 异常体系
运行时异常:RuntimeException及其子类,编译阶段不会出现错误提醒,运行时出现的异常(如:数组引越界异常)
编译时异常:编译阶段就会出现错误提醒的。(如:日期解析异常)
- 方法:
public static void main(String[] args){
try{
} catch (ParseException e){
e.printStackTrace()
}
}
public static void main(String[] args) throws ParseException{
}
自定义异常
- 种类
运行异常:
public static void main(String[] args) {
try {
saveAge(20);
System.out.println("执行成功!");
} catch (Exception e) {
e.printStackTrace();
System.out.println("执行失败!");
}
}
public static void saveAge(int age){
if(age > 0 && age < 150) {
System.out.println("保存成功!:" + age);
}else{
throw new AgeIllegalRuntimeException("问题原因:年龄非法");
}
}
public class AgeIllegalRuntimeException extends RuntimeException{
public AgeIllegalRuntimeException() {
}
public AgeIllegalRuntimeException(String message) {
super(message);
}
}
编译时异常(注意要在方法上使用thows处理异常对象):
问题严重时使用,
public static void main(String[] args) {
try {
saveAge(20);
System.out.println("执行成功!");
} catch (Exception e) {
e.printStackTrace();
System.out.println("执行失败!");
}
}
public static void saveAge(int age) throws AgeIllegalException{
if(age > 0 && age < 150) {
System.out.println("保存成功!:" + age);
}else{
//throw 抛出这个异常对象
//thows 用在方法上,抛出方法内部异常
throw new AgeIllegalException("问题原因:年龄非法");
}
}
PS:写时就报错
public class AgeIllegalException extends Exception{
public AgeIllegalRuntimeException() {
}
public AgeIllegalRuntimeException(String message) {
super(message);
}
}
异常处理
- thows:抛给上层函数
- try-catch:异常捕获
-
开发中常见处理方式
-
记录异常并返回合适信息给用户:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Test2 {
public static void main(String[] args) {
try {
fun1();
} catch (FileNotFoundException e) {
System.out.println("文件不存在!");
e.printStackTrace();
} catch (ParseException e) {
System.out.println("解析时间有问题!");
e.printStackTrace();
}
}
public static void fun1() throws FileNotFoundException, ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = sdf.parse("2028-11-11 10:24:11");
System.out.println(d);
func2();
}
public static void func2() throws FileNotFoundException {
InputStream is = new FileInputStream("D:/lan.txt");
}
}
推荐写法:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Test3 {
public static void main(String[] args) {
try {
fun1();
} catch (Exception e) {
System.out.println("解析时间有问题!");
e.printStackTrace();
}
}
public static void fun1() throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = sdf.parse("2028-11-11 10:24:11");
System.out.println(d);
func2();
}
public static void func2() throws Exception {
InputStream is = new FileInputStream("D:/lan.txt");
}
}
- 尝试修复异常:
集合框架
整体概述
- 单列结合Collection、双列集合Map
Collection集合(接口)
Collection体系结构
- List系列集合:添加元素是有序、可重复、有索引
(ArrayList、LinkedList:有序、可重复、有索引)- set系列集合:添加元素是无有序、不重复、无索引
(HashsSet:无序、不重复、无索引;
LinkedHashSet:有序、无重复、无索引
TreeSet:按照大小默认升序排序、无重复、无索引)
Collection常用方法
- Collection是所有单列结合的祖宗,其规定的方法全部单列集合都会继承。
public static void main(String[] args) {
Collection<String> c = new ArrayList<>(); //多态写法
//添加,成功返回True
c.add("java1");
c.add("java1");
c.add("java2");
c.add("java3");
System.out.println(c);
//清空集合元素
// c.clear();
System.out.println(c);
//判断集合为空
System.out.println(c.isEmpty());
//获取集合大小
System.out.println(c.size());
//判断集合中是否包含某个元素
System.out.println(c.contains("java1")); //True
System.out.println(c.contains("Java1")); //False
//删除某个元素,多个重复元素默认删除第一个
System.out.println(c.remove("java1"));
//集合转换成数组(Object数组)
Object[] arr = c.toArray();
System.out.println(Arrays.toString(arr));
String[] strArr = c.toArray(new String[c.size()]); //转成指定类型数组
//把一个集合全部数据倒入到另一个集合中
Collection<String> c2 = new ArrayList<>();
c2.add("java_1");
c2.add("java_2");
c.addAll(c2); //c2集合数据不变
}
Collection遍历方式
因为Collection下set系列集合无序,所以不支持不同for循环进行遍历。
- 迭代器
- 迭代器是用来遍历集合的专用方式(数组没有迭代器),在Java中迭代器的代表是Iterator。
public static void main(String[] args) {
Collection<String> c = new ArrayList<>();
c.add("java");
c.add("python");
c.add("cpp");
c.add("c");
//使用Iterator遍历集合
//1.从集合对象中获取迭代器对象
Iterator<String> i = c.iterator();
// System.out.println(i.next());
// System.out.println(i.next());
// System.out.println(i.next());
// System.out.println(i.next());
// System.out.println(i.next()); //出现异常
//2.使用循环+迭代器遍历集合
while(i.hasNext()){
System.out.println(i.next());
}
}
- 增强for
-
== 增强for可以用来遍历集合或是数组==
-
本质是迭代器遍历集合的简化写法
public static void main(String[] args) {
Collection<String> c = new ArrayList<>();
c.add("java");
c.add("python");
c.add("cpp");
c.add("c");
//使用增强for遍历集合
for(String ele :c){
System.out.println(ele);
}
}
- lambda表达式
- 得益于JDK 8开始的新技术Lambda表达式,提供了一种更简单、更直接的方式来遍历集合
PS:遍历Map集合十分简单!!
public static void main(String[] args) {
Collection<String> c = new ArrayList<>();
c.add("java");
c.add("python");
c.add("cpp");
c.add("c");
//lambda表达式遍历集合
// c.forEach(new Consumer<String>() {
// @Override
// public void accept(String s) {
// System.out.println(s);
// }
// });
//
// c.forEach((String s) -> {
// System.out.println(s);
// });
//
// c.forEach(s -> {
// System.out.println(s);
// });
//
// c.forEach(s -> System.out.println(s));
c.forEach(System.out::println); //方法引用
}
List集合(接口)
特有方法
- List集合因为支持索引,所以多了很多与索引相关的方法
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("java1");
list.add("java2");
list.add("java3");
System.out.println(list);
//在某个索引插入数据
list.add(2, "java_insert");
//根据索引删除元素,返回被删除元素
System.out.println(list.remove(2));
//根据索引取元素
System.out.println(list.get(0));
//修改指定索引的元素,修改成功后返回原元素
System.out.println(list.set(1,"java_1"));
}
遍历方式
- for循环
- 迭代器
- 增强for
- lambda表达式
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("java1");
list.add("java2");
list.add("java3");
System.out.println(list);
//for循环
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
//迭代器
Iterator<String> it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
//增强for,(foreach遍历)
for(String s: list){
System.out.println(s);
}
//lambda
list.forEach(s -> {
System.out.println(s);
});
}
ArrayList(实现类)
-
ArrayLis集合底层原理
-
ArrayList集合适合的应用场景
- ArrayList适合:根据索引查询数据,比如根据随机索引取数据(高效)!或者数据量不是很大时!
- ArrayList不适合:数据量大的同时又要频繁的进行增删操作!
LinkedList(实现类)
- LinkedList集合底层原理
- 基于双链表实现的。
- 特点:查询慢,增删相对较快,但对首尾元素进行增删改查的速度是极快的,
新增方法
应用场景(队列、栈)
- 设计队列
public static void main(String[] args) {
//1.创建一个队列
LinkedList<String> queue = new LinkedList<>();
//入队
queue.addLast("第一号人");
queue.addLast("第二号人");
queue.addLast("第三号人");
//出队
System.out.println(queue.removeFirst());
System.out.println(queue.removeFirst());
System.out.println(queue.removeFirst());
}
2.设计栈
public static void main(String[] args) {
LinkedList<String> stack = new LinkedList<>();
//压栈
// stack.addFirst("第一颗子弹");
// stack.addFirst("第二颗子弹");
// stack.addFirst("第三颗子弹");
stack.push("第一颗子弹");
stack.push("第二颗子弹");
stack.push("第三颗子弹");
//出栈
// System.out.println(stack.removeFirst());
// System.out.println(stack.removeFirst());
// System.out.println(stack.removeFirst());
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
}
Set集合(接口)
特点
- HashSet:无序、不重复、无索引。
- LinkedHashSet:有序、不重复、无索引。
- TreeSet:排序(升序)、不重复、无索引。
public static void main(String[] args) {
// Set<Integer> set = new HashSet<>(); //经典代码
// Set<Iterator> set = new LinkedHashSet<>();
Set<Iterator> set = new TreeSet<>();
set.add(666);
set.add(666);
set.add(777);
set.add(888);
System.out.println(set);
PS:Set要用到的常用方法,基本上就是Collection提供的!自己几乎没有额外新增。
HashSet(实现类)
- 无序、不重复、无索引
哈希值
- 哈希值:一个int类型的整数,java中每一个对象都有一个哈希值
- Java中的所有对象,都可以调用0bejct类提供的hashCode方法,返回该对象自己的哈希值。
- 同一个对象多次调用hashCode()方法返回的哈希值是相同的。
- 不同的对象,它们的哈希值一般不相同,但也有可能会相同(哈希碰撞)
- 基于哈希表实现。
- Hashset集合默认不能对内容一样的两个不同对象去重复!eg.内容一样的两个学生对象存入到HashSet集合中去,Hashset集合是不能去重复的!
如何让Hashset集合能够实现对内容一样的两个不同对象也能去重复??
如果希望Set集合认为2个内容一样的对象是重复的必须重写对象的hashCode()和equals()方法
public class Student {
public String name;
public int age;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
LinkedHashSet (实现类)
- 有序(添加元素和获取元素顺序一直)、不重复、无索引
- 依然是基于 哈希表实现的。
- 但是,它的每个元素都额外的多了一个双链表的机制记录它前后元素的位置
TreeSet(实现类)
- 不重复、无索引、可排序(升序)
- 底层是基于红黑树实现的排序
- 对于数值类型:Integer,Double,默认按照数值本身的大小进行升
- 序排序对于字符串类型:默认按照首字符的编号升序排序。
- 对于自定义类型如Student对象,Treeset默认是无法直接排序的。
自定义排序规则
方式一
- 让自定义的类(如学生类)实现Comparable接口,重写里面的compareTo方法来指定比较规则。
public class Student implements Comparable<Student> {
public String name;
public int age;
public double height;
@Override
public int compareTo(Student o) {
return this.age-o.age;
}
}
方式二
- 通过调用TreeSet集合有参数构造器,可以设置Comparator对象(比较器对象,用于指定比较规则。)
public static void main(String[] args) {
//TreeSet对象
Set<Student> students = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return Double.compare(o1.height, o2.height);
}
});
}
PS:TreeSet会就近选择自己自带的比较器对象进行排序
实现类场景总结
Collections(工具类)
前置知识:可变参数
- 就是一种特殊形参,定义在方法、构造器的形参列表里,格式是:数据类型…参数名称
- 特点:可以不传数据给它;可以传一个或者同时传多个数据给它;也可以传一个数组给它
- 好处:常常用来灵活的接收数据。
PS:
- 可变参数在方法内部就是一个数组
- 一个形参列表只能有一个可变参数
- 必须放在形参列表的最后面
public static void main(String[] args) {
test();
test(10);
test(10,20,30);
test(new int[]{10,20,30,40});
}
public static void test(int...nums){
System.out.println("长度:"+nums.length);
System.out.println(Arrays.toString(nums));
System.out.println("---------------");
}
静态方法
- 操作集合的工具类
public static void main(String[] args) {
//1.addAll:为集合添加一批数据
List<String> names = new ArrayList<>();
Collections.addAll(names,"张三","李四","王五");
System.out.println(names);
//shuffle打乱List系列集合的顺序
Collections.shuffle(names);
System.out.println(names);
//对List系列集合进行升序排序
//自定义对象:方法一,类实现比较规则;方法二,比较器
List<Double> list = new ArrayList<>();
Collections.addAll(list,12.4,22.5,694.2);
Collections.sort(list, new Comparator<Double>() { //降序
@Override
public int compare(Double o1, Double o2) {
return -Double.compare(o1,o2);
}
});
System.out.println(list);
}
Map集合(接口)
Map体系结构
- Map系列集合的特点都是由键决定的,值只是一个附属品(不做要求)
- HashMap:无序、不重复、无索引
- LinkedHashMap:有序(按照添加顺序)、不重复、无索引
- TreeMap:按照键大小升序排序、不重复、无索引
public static void main(String[] args) {
Map<String,Integer> map = new HashMap<>(); //经典代码
map.put("手表",100);
map.put("手机",84);
map.put("书",100);
map.put(null,null);
System.out.println(map);
}
Map常用方法
Map遍历方式
- 键找值
public static void main(String[] args) {
Map<String,Double> map = new HashMap<>();
map.put("蜘蛛精",162.5);
map.put("紫霞",170.2);
map.put("牛魔王",196.9);
map.put("至尊宝",179.4);
System.out.println(map);
Set<String> keys = map.keySet();
System.out.println(keys);
for (String key : keys) {
double value = map.get(key);
System.out.println(value);
}
}
- 键值对
public static void main(String[] args) {
Map<String,Double> map = new HashMap<>();
map.put("蜘蛛精",162.5);
map.put("紫霞",170.2);
map.put("牛魔王",196.9);
map.put("至尊宝",179.4);
System.out.println(map);
//1.调用entrySet
Set<Map.Entry<String, Double>> entries = map.entrySet();
for (Map.Entry<String, Double> entry:entries) {
String key = entry.getKey();
Double value = entry.getValue();
System.out.println(key+"----->"+value);
}
}
- Lambda表达式
在这里插入图片描述
public static void main(String[] args) {
Map<String,Double> map = new HashMap<>();
map.put("蜘蛛精",162.5);
map.put("紫霞",170.2);
map.put("牛魔王",196.9);
map.put("至尊宝",179.4);
System.out.println(map);
// map.forEach(new BiConsumer<String, Double>() {
// @Override
// public void accept(String s, Double aDouble) {
// System.out.println(s+"--->"+aDouble);
// }
// });
map.forEach((k,v)->{
System.out.println(k+"————>"+v);
});
}
代码原理
HashMap(实现类)
- HashMap:无序、不重复、无索引
- HashMap跟Hashset的底层原理是一一样的,都是基于哈希表实现的。
- JDK8之前,哈希表=数组+链表
- JDK8开始,哈希表=数组+链表+红黑树
- HashMap的键依赖hashCode方法和equals方法保证键的唯一
- 自定义类需要重写类的equals方法和hashCode方法
实际上,原来学的Set系列集合的底层就是基于Map实现的,只是Set集合中的元素只要键数据,不要值数据而已。 (使用键计算哈希值)
LinkedHashMap(实现类)
- LinkedHashMap:有序(按照添加顺序)、不重复、无索引
实际上:原来学习的LinkedHashset集合的底层原理就是LinkedHashMap,
TreeMap(实现类)
- TreeMap:按照键大小升序排序、不重复、无索引
补充:集合的并发修改异常问题
- 使用迭代器遍历集合,但用迭代器自己的删除方法删除数据即可,
- 如果能用for循环遍历时:可以倒着遍历并删除;或者从前往后遍历,但删除元素后做i --操作。
PS:
增强for或是lambda遍历方法无法解决并发修改异常问题
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("王麻子");
list.add("小李子");
list.add("李爱花");
list.add("李玉刚");
System.out.println(list);
//需求:遍历集合中全部带”李“的名字,并从集合删除
// Iterator<String> it = list.iterator();
// while(it.hasNext()){
// String name = it.next();
// if(name.contains("李")){
// list.remove(name);
// }
// }
// System.out.println(list); //异常!!!!!、
Iterator<String> it = list.iterator();
while(it.hasNext()){
String name = it.next();
if(name.contains("李")){
it.remove(); //删除迭代器当前遍历到的数据,没删除一个数据后,相当于做了一个i--
}
}
System.out.println(list); //异常!!!!!
}
补充:集合嵌套
public static void main(String[] args) {
Map<String, List<String>> map = new HashMap<>();
List<String> cityList1 = new ArrayList<>();
Collections.addAll(cityList1,"南京","无锡");
map.put("江苏省",cityList1);
List<String> cityList2 = new ArrayList<>();
Collections.addAll(cityList2,"石家庄","保定");
map.put("河北省",cityList2);
System.out.println(map);
}
JDK8新特性:Stream
- 什么是Stream?
也叫Stream流,是]dk8开始新增的一套APl (java.util.stream."),可以用于操作集合或者数组的数据。 - 优势: Stream流大量的结合了Lambda的语法风格来编程,提供了一种更加强大,更加简单的方式操作集合或者数组中的数据,代码更简洁,可读性更好。
public static void main(String[] args) {
List<String> names = new ArrayList<>();
Collections.addAll(names,"张三丰","张翰","李四","张无忌");
//需求:把姓张的,且三字的名字加入listZhang
List<String> listZhang =new ArrayList<>();
// for(String name:names){
// if(name.startsWith("张") && name.length()==3){
// listZhang.add(name);
// }
// }
names.stream().filter(s -> s.startsWith("张")).
filter(a -> a.length()==3);
}
常用方法
- 获取Stream流
Map的Stream流
public static void main(String[] args) {
Map<String,Double> map = new HashMap<>();
map.put("迪丽热巴",172.3);
map.put("古力娜扎",168.6);
Set<String> key = map.keySet();
Collection<Double> value = map.values();
Stream<String> ks = key.stream();
Stream<Double> vs = value.stream();
Set<Map.Entry<String,Double>> entries = map.entrySet();
Stream<Map.Entry<String,Double>> es = entries.stream();
es.filter(e -> e.getValue() >= 170.0).forEach(e -> System.out.println(e));
}
- 调用中间方法
- 调用终结方法
- 收集Stream流
流只能收集一次!!
public static void main(String[] args) {
List<String> names = new ArrayList<>();
Collections.addAll(names,"张三丰","张翰","李四","张无忌");
//需求:把姓张的,且三字的名字加入listZhang
// List<String> listZhang =new ArrayList<>();
// for(String name:names){
// if(name.startsWith("张") && name.length()==3){
// listZhang.add(name);
// }
// }
List<String> listZhang = names.stream().filter(s -> s.startsWith("张") && s.length()==3).
collect(Collectors.toList());
}
字符集
File
- File是java.io.包下的类, File类的对象,用于代表当前操作系统的文件(可以是文件、或文件夹)
- File类只能对文件本身进行操作,不能读写文件里面存储的数据。
创建对象
PS:
- File对象可以指代一个不存在的文件路径
- 相对路径默认起点为工程根目录,“模块名\\a.txt”
public static void main(String[] args) {
//路径分隔符
//1.
File f1 = new File("C:\\Users\\22827\\Desktop\\访问控制.txt");
//2.
File f2 = new File("C:/Users/22827/Desktop/访问控制.txt");
//3.
File f3 = new File("C:"+File.separator+"Users/22827/Desktop/访问控制.txt");
System.out.println(f3.length());
}
常用方法
- 判断文件类型、获取文件信息
- 创建文件、删除文件
public static void main(String[] args) throws IOException {
File f1 = new File("src\\d_30_FileDemo\\a.txt");
f1.createNewFi
1. List item
le();
}
- 遍历文件夹(一级)
public static void main(String[] args) throws IOException {
File f1 = new File("src");
File[] dirList = f1.listFiles();
for(File f : dirList){
System.out.println(f.getAbsoluteFile());
}
}
- 文件搜索(多级)
用递归做
public static void search(File f, String searchName){
//1.拦截非法情况
if(f == null || !f.exists() || f.isFile()) {return;}
//2.拿到一级目录对象
File[] dirs = f.listFiles();
//3.判断当前目录下存在一级文件对象,以及可以拿到一级对象
if(dirs!= null && f.length()>0){
//4.遍历
for (File name : dirs) {
//5.判断是文件还是文件夹
if(name.isFile()){
}
else{
search(name,searchName);
}
}
}
}
IO流
流使用完之后必须关闭!!释放系统资源
IO流的分类
IO流总体四大流:字节输入流、字节输出流、字符输入流、字符输出流
IO流的体系结构
字节流
FileInputStream(实现类)
- 单字节读取
public static void main(String[] args) throws Exception {
//1.创建文件字节输入流管道与源文件接通
//InputStream is = new FileInputStream(new File("Project1\\src\\a.txt"));
//简化写法,推荐使用
InputStream is = new FileInputStream("src\\a.txt");
//2.读取数据,如果没有数据返回-1
int b;
while((b = is.read()) != -1){
System.out.print(b); //#使用print函数每次打印时不换行
}
is.close();
//读取性能很差。
//读取汉字会乱码!且无法解决。
}
- 多字节读取
public static void main(String[] args) throws Exception{
FileInputStream is = new FileInputStream("src\\File\\1.txt");
byte[] buffer = new byte[3];
int len; //记录每次读取多少字节
while((len = is.read(buffer)) != -1){
String rs = new String(buffer,0,len);
System.out.println(rs);
}
is.close();
}
}
PS: 使用字节输入流读取文件,性能得到提升,但读取汉字输入会乱码!
- 文件全部字节
方法一:
public static void main(String[] args) throws Exception{
FileInputStream is = new FileInputStream("src\\File\\1.txt");
File f = new File("src\\File\\1.txt");
long size = f.length();
byte[] buffer = new byte[(int)size];
int len =is.read(buffer); //记录每次读取多少字节
System.out.println(new String(buffer));
}
方法二:
public static void main(String[] args) throws Exception{
FileInputStream is = new FileInputStream("src\\File\\1.txt");
byte[] buffer = is.readAllBytes();
System.out.println(new String(buffer));
}
FileOutputStream(实现类)
public static void main(String[] args) throws Exception {
OutputStream os =new FileOutputStream("src\\File\\out.txt");
os.write(97); //97就是一个字节,代表a
os.write('b'); //'b'也是一个字节
//换行符
os.write("\r\n".getBytes());
byte[] bufffer = "我爱中国".getBytes();
os.write(bufffer);
os.close();
}
PS:字节流非常适合文件复制
字符流
默认使用UTF-8读写字符
FileReader(实现类)
- 单字符
public static void main(String[] args) throws FileNotFoundException {
try(
Reader fr = new FileReader("src\\File\\1.txt");
){
int c; //用于每次读取的字符编号
while((c = fr.read()) != -1){
System.out.print((char)c);
}
} catch(Exception e){
e.printStackTrace();
}
}
- 多字符
public static void main(String[] args) throws FileNotFoundException {
try(
Reader fr = new FileReader("src\\File\\1.txt");
){
char[] buffer = new char[3]; //字符数组
int len; //每次读取了多少个字符
while ((len = fr.read(buffer)) != -1){
System.out.print(new String(buffer,0,len));
}
} catch(Exception e){
e.printStackTrace();
}
}
FileWriter(实现类)
PS:
字符输出流写出数据后,必须刷新流,或者关闭流,写出去的数据才能生效。(因此字符输出流先写在内存的缓冲区,通过刷新、关闭流操作再一次性同步)
缓冲流
字节缓存流
- 原理:字节缓冲输入流自带了8KB缓冲池;字节缓冲输出流也自带了8KB缓冲池,
public static void main(String[] args) {
try(
InputStream bis = new BufferedInputStream(new FileInputStream("src\\File\\1.txt"),1024*16); //16KB
OutputStream bos = new BufferedOutputStream(new FileOutputStream("src\\File\\out.txt"),1024*1024*8); //8MB
){
}catch(Exception e){
e.printStackTrace();
}
}
字符缓冲流
- 输入
public static void main(String[] args) {
try(
//为了调用BufferedReader新增方法,不使用多态写法
BufferedReader br = new BufferedReader(new FileReader("src\\File\\1.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("src\\File\\out.txt"));
){
String line;
while((line = br.readLine()) != null){
System.out.println(line);
}
}catch(Exception e){
e.printStackTrace();
}
}
- 输出
public static void main(String[] args) {
try(
//为了调用BufferedReader新增方法,不使用多态写法
BufferedReader br = new BufferedReader(new FileReader("src\\File\\1.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("src\\File\\out.txt"));
){
bw.write("第一行");
bw.write("第一行");
bw.newLine();
bw.write("第二行");
}catch(Exception e){
e.printStackTrace();
}
}
原始流、缓冲流性能分析
当原始字节输入流按照字节数组读入时,若自己数组足够大,性能不一定比字节缓冲输入流差
转换流
- 默认使用UTF-8读写字符
- 解决思路:先获取文件的原始字节流,再将其按真实的字符集编码转成字符输入流,这样字符输入流中的字符就不乱码了。
字符输入转换流
public static void main(String[] args) {
try(
//1.转换流
Reader irs = new InputStreamReader(new FileInputStream("src\\File\\2.txt"),"GBK");
//2.再包装成字符缓冲输入流
BufferedReader br = new BufferedReader(irs);
){
String line;
while ((line = br.readLine())!=null){
System.out.println(line);
}
}catch (Exception e){
e.printStackTrace();
}
}
字符输出转换流
- 作用:可以控制写出去的字符使用什么字符集编码。
- 解决思路:获取字节输出流,再按照指定的字符集编码将其转换成字符输出流,以后写出去的字符就会用该字符集编码了
public static void main(String[] args) {
try(
//1.转换流
Writer osw = new OutputStreamWriter(new FileOutputStream("src\\File\\out2.txt"),"GBK");
//2.包装
BufferedWriter bw = new BufferedWriter(osw);
){
bw.write("我爱你中国");
bw.newLine();
bw.write("中12华");
}catch (Exception e){
e.printStackTrace();
}
}
打印流
- 作用:打印流可以实现更方便、更高效的打印数据出去,能实现打印啥出去就是啥出去。
PrintStream
public static void main(String[] args) {
try(
PrintStream ps = new PrintStream("src/out3.txt", Charset.forName("GBK"));
) {
ps.println(97); //97
ps.println('a'); //'a'
ps.println(99.5); //99.5
ps.println(true); //true
ps.write(97); //'a'
} catch (Exception e) {
e.printStackTrace();
}
}
PrintWriter
- PrintWriter与PrintStream在打印数据(println方法)上没有区别,在写数据(write方法)上才有区别。
try(
PrintWriter ps = new PrintWriter("src/out3.txt", true);
) {
ps.println(97);
ps.println('a');
ps.println(99.5);
ps.println(true);
ps.write(97); //'a'
} catch (Exception e) {
e.printStackTrace();
}
}
- 把System.out.println打印到具体文件中去
public static void main(String[] args) {
try(
PrintStream ps = new PrintStream("src/out3.txt");
) {
System.setOut(ps);
System.out.println("摩西摩西");
} catch (Exception e) {
e.printStackTrace();
}
}
数据流
- 运行把数据及其类型一并写出去
public static void main(String[] args) {
try(
DataOutputStream dos = new DataOutputStream(new FileOutputStream("src/out4.txt"));
) {
dos.writeInt(97);
dos.writeDouble(96.7);
dos.writeBoolean(true);
dos.writeUTF("兰州大学");
} catch (Exception e) {
e.printStackTrace();
}
}
- 读入数据输出流写出的文件
public static void main(String[] args) {
try(
DataInputStream dis = new DataInputStream(new FileInputStream("src/out4.txt"));
) {
//必须与写入顺序保持一致
System.out.println(dis.readInt());
System.out.println(dis.readDouble());
System.out.println(dis.readBoolean());
System.out.println(dis.readUTF());
} catch (Exception e) {
e.printStackTrace();
}
}
序列化流
- 对象序列化:把java对象写入到文件中去。
- 对象反序列化:把文件里的java对象读出来
- 序列化
java对象
必须实现Serializable接口
transient修饰的成员变量不参与序列化
public class Student implements Serializable{
private String name;
private transient int age; //transient修饰的成员变量不参与序列化
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
序列化代码
public static void main(String[] args) {
Student s1 = new Student("李笑",24);
try(
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/File/out5.txt"));
) {
oos.writeObject(s1);
System.out.println("序列化对象s1成功!!");
} catch (Exception e) {
e.printStackTrace();
}
}
- 反序列化
public static void main(String[] args) {
try(
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src/File/out5.txt"));
) {
//读出Object类型对象,然后强转
Student s1 = (Student)ois.readObject();
System.out.println(s1); //Student类依旧重写了toString方法
} catch (Exception e) {
e.printStackTrace();
}
}
PS:
IO框架
- 什么是框架?
解决某类问题,编写的一套类、接口等,可以理解成一个半成品,大多框架都是第三方研发的。 - IO框架:封装了Java提供的对文件、数据进行操作的代码,对外提供了更简单的方式来对文件进行操作,对数据进行读写等。
框架导入方法
Commons-io
- Commons-io是apache开源基金组织提供的一组有关10操作的小框架,目的是提高IO流的开发效率。
释放资源的方式
- try-catch-finally
即使try中由return,也会执行finally中的代码。
public static void main(String[] args) {
OutputStream os = null;
try{
os = new FileOutputStream("src\\File\\out.txt");
byte[] bufffer = "我爱中国".getBytes();
os.write(bufffer);
}catch(Exception e) {
e.printStackTrace();
}finally {
try {
if(os != null) os.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
- try-catch-resource
public static void main(String[] args) {
try(
OutputStream os = new FileOutputStream("src\\File\\out.txt");
//这里只能放置资源对象!!
//什么是资源对象?资源都是实现AutoCloseable接口
//自动调用资源的close方法,完成资源释放
){
byte[] bufffer = "我爱中国".getBytes();
os.write(bufffer);
}catch(Exception e) {
e.printStackTrace();
}
}