JavaSE
——C77eric
[TOC]
面向对象OOP 类变量
什么是 类变量
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。
如何定义类变量
定义语法
访问修饰符 static 数据类型 变量名
如何访问类变量
类名.类变量名
或者 对象名.类变量名 静态变量的访问修饰符的访问权限和范围 和 普通属性是一样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static void mian (String[] args) {System.out.println(A.name); A a = new A ();System.out.println(a.name); } class A {public static String name = "csq" ;}
类变量的使用注意事项和细节 I.什么时候需要用类变量
当我们需要让某个类的所有对象都共享一个变量时,就可以考虑是类变量(静态变量)。比如: 定义学生类,统计所有学生共交多少钱,Student(name,static fee)
II.类变量和实例(普通属性)变量的区别
类变量是该类的所有对象共享的,而实例变量是每个对象独享的
III.加上 static 称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
VI.类变量可以通过类名.类变量名 或 对象名.类变量名 访问 前提是满足修饰符的访问权限和范围
V.实例变量不能通过 类名.类变量名 方式访问
VI.类变量是在类加载的时候就初始化了,也就是说,即使没有创建对象,只要类加载了,就可以使用类变量
VII.类变量的生命周期是随着类的加载开始,随着类的消亡而销毁
类方法
基本介绍
类方法也叫静态方法
形式如下
访问修饰符 static 数据返回类型 方法名(){}
类方法的调用 类名.类方法名 对象名.类方法名 前提是满足访问修饰符的访问权限和范围
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public static void main (String[] args) { Stu tom = new Stu ("tom" ); Stu.payFee(100 ); Stu mary = new Stu ("mary" ); Stu.payFee(200 ); Stu.showFee(); System.out.println("9 开平方的结果是=" + Math.sqrt(9 )); System.out.println(MyTools.calSum(10 , 30 )); } } class MyTools { public static double calSum (double n1, double n2) { return n1 + n2; } class Stu { private String name; private static double fee = 0 ; public Stu (String name) { this .name = name; } public static void payFee (double fee) { Stu.fee += fee; } public static void showFee () { System.out.println("总学费有:" + Stu.fee); } }
使用场景 当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率
如果我们希望不创建实例,也可以调用某个方法(即当作工具使用)
比如: 工具类中方法 utils
Math 类、Array 类、Collections 集合类
小结:
在程序实际开发中,往往会将一些通用的方法,设计成静态方法,这样我们不需要创建对象就可以使用,比如打印一维数组,冒泡排序,完成某个计算任务
注意事项和细节 I. 类方法和普通方法都是随着类的加载而加载,将结构信息储存在方法区:
类方法中无 this 的参数
普通方法中隐含着 this 的参数
II.类方法可以通过类名调用,也可以通过对象名调用
III.普通方法和对象有关,需要通过对象名调用,比如对象名.方法名(参数),不能通过类名调用
VI.类方法中不允许使用和对象有关的关键字,比如 this 和 super 。普通方法(成员方法)可以
V.类方法(静态方法)中只能访问 静态变量 或 静态方法
VII.普通成员方法,既可以访问 非静态成员,也可以访问静态成员
小结:
静态方法,只能访问静态的成员,非静态的方法,可以访问静态成员和非静态成员 前提是必须遵守访问权限和范围
main() 方法
形式 1 public static void main (String [] args) {}
说明 1. main 方法是虚拟机调用
**2. Java 虚拟机需要调用类的 main 方法,所以该方法的访问权限必须是 public **
3. Java 虚拟机在执行 main 方法时不必创建对象,所以该方法必须是 static
4.该方法接收 String 类型的数组参数,该数组中保存执行 Java 命令时传递给所运行的类的参数
**5. Java 执行的程序 参数1 参数2 参数3 **
特别提示
1.在 main 方法中,我们可以直接调用 main 方法所在类的静态方法 或 静态属性
2. 但是不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才可以通过这个对象去访问类中的非静态成员
代码块
基本介绍
代码块又称 初始化块 ,属于类中的成员[即 是类的一部分],类似于方法,将逻辑语句封装在方法体中,通过 {} 包围起来
但和方法不同,没有方法名 ,没有返回,没有参数,只有方法体,而且不用通过对象或类 显式调用,而是加载类时,或创建对象时隐式调用
基本语法
说明
修饰符可选,要写的话,也只能写 static
代码块分为两类,使用 static 修饰的叫 静态代码块,没有 static 修饰的,叫普通代码块/非静态代码块
逻辑语句可以为任何逻辑语句(输入,输出,方法调用,循环,判断等)
;可以写,也可以省略
代码块的好处
相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作
场景: 如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class CodeBlock01 {public static void main (String[] args) { Movie movie = new Movie ("你好,李焕英" ); System.out.println("===============" ); Movie movie2 = new Movie ("唐探 3" , 100 , "陈思诚" ); } } class Movie { private String name; private double price; private String director; System.out.println("电影屏幕打开..." ); System.out.println("广告开始..." ); System.out.println("电影正是开始..." ); }; public Movie (String name) { System.out.println("Movie(String name) 被调用..." ); this .name = name; } public Movie (String name, double price) { this .name = name; this .price = price; } public Movie (String name, double price, String director) { System.out.println("Movie(String name, double price, String director) 被调用..." ); this .name = name; this .price = price; this .director = director; } }
使用注意事项和细节
static 代码块也叫做 静态代码块,作用就是对类进行初始化,而且谈随着 类的加载 而执行,并且只会执行一次,如果是普通代码块,每创建一个对象,就执行一次
类什么时候被加载
创建对象实例的时候(new)
创建子类对象实例的时候,父类也会被加载
使用类的静态成员时(静态属性/静态方法)
普通的代码块,在创建对象实例时,会被隐式调用,被创建一次,调用一次 如果只是使用类的静态成员时,普通代码块并不会执行
小结
1.static 代码块是在类加载时,执行,且只执行一次
2.普通代码块是在创建对象时调用的,创建一次,调用一次
3.类加载的 3 种 情况需要记住
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 public static void main (String[] args) { System.out.println(DD.n1); } } class DD { public static int n1 = 8888 ; static { System.out.println("DD 的静态代码 1 被执行..." ); } { System.out.println("DD 的普通代码块..." ); } } class Animal { static { System.out.println("Animal 的静态代码 1 被执行..." ); } } class Cat extends Animal { public static int n1 = 999 ; static { System.out.println("Cat 的静态代码 1 被执行..." ); } } class BB { static { System.out.println("BB 的静态代码 1 被执行..." ); } } class AA extends BB { static { System.out.println("AA 的静态代码 1 被执行..." ); }; }
4.创建一个对象时,在 一个类 调用顺序是(重点、难点)
调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)
调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和普通属性初始化,则按定义顺序调用
调用构造方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public static void main (String[] args) { A a = new A (); } } class A { { System.out.println("A 普通代码块 01" ); } private int n2 = getN2(); static { System.out.println("A 静态代码块 01" ); } private static int n1 = getN1(); public static int getN1 () { System.out.println("getN1 被调用..." ); return 100 ; } public int getN2 () { System.out.println("getN2 被调用..." ); return 200 ; } public A () { System.out.println("A() 构造器被调用" ); } }
5.构造器的最前面其实隐含了 super() 和 调用普通代码块,静态相关的代码块,属性初始化,在类加载时,就加载完毕,因此是优先于 构造器和 普通代码块执行的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class CodeBlockDetail03 { public static void main (String[] args) { new BBB (); } } class AAA { { System.out.println("AAA 的普通代码块" ); } public AAA () { System.out.println("AAA() 构造器被调用...." ); } } class BBB extends AAA { { System.out.println("BBB 的普通代码块..." ); } public BBB () { System.out.println("BBB() 构造器被调用...." ); } } class BBB extends AAA { { System.out.println("BBB 的普通代码块..." ); } public BBB () { System.out.println("BBB() 构造器被调用...." ); } }
6.创建一个子类对象时(继承关系) ,他们的 静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下
父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
父类的构造方法
子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
子类的构造方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 public static void main (String[] args) { new C02 (); } } class A02 { private static int n1 = getVal01(); static { System.out.println("A02 的一个静态代码块.." ); } { System.out.println("A02 的第一个普通代码块.." ); } public int n3 = getVal02(); public static int getVal01 () { System.out.println("getVal01" ); return 10 ; } public int getVal02 () { System.out.println("getVal02" ); return 10 ; } public A02 () { } } class C02 { private int n1 = 100 ; private static int n2 = 200 ; private void m1 () { } private static void m2 () { } static { System.out.println(n2); m2(); } { System.out.println(n1); System.out.println(n2); m1(); m2(); } } class B02 extends A02 { private static int n3 = getVal03(); static { System.out.println("B02 的一个静态代码块.." ); } public int n5 = getVal04(); { System.out.println("B02 的第一个普通代码块.." ); } public static int getVal03 () { System.out.println("getVal03" ); return 10 ; } public int getVal04 () { System.out.println("getVal04" ); return 10 ; } }
7.静态代码块只能调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员
设计模式
什么是设计模式
静态方法和属性的经典使用
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编码风格、以及解决问题的思考方式。设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免去我们自己再思考和摸索
单例模式 什么是单例
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
单例模式有两种方式 :1.饿汉式 2.懒汉式
单例模式应用实例 - 饿汉式 步骤如下
构造器私有化 -> **防止用户直接 new **
类的内部创建对象
向外暴露一个静态的公共方法 - getInstance
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class GirlFriend { private String name; private static GirlFriend gf = new GirlFriend ("q" ); private GirlFriend (String name) { this .name = name; } public static GirlFriend getInstance () { return gf; } }
饿汉式可能造成创建了对象,但是没有使用,造成资源浪费
单例应用案例 - 懒汉式 步骤如下
构造器私有化
定义一个 static 静态属性对象
提供一个公共的 static 方法,可以返回一个对象
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Cat { private String name; private static Cat cat; private Cat (String name) { this .name = name; } public static Cat () { if (cat == null ){ cat = new Cat ("xx" ); } return cat; } }
类加载之后,构造器也不会调用
懒汉式和饿汉式的区别
两者最主要的区别在于创建对象的时机不同:饿汉式是在类加载时就创建了对象实例,而懒汉式是在使用时才创建
饿汉式不存在线程安全问题,懒汉式存在线程安全问题
饿汉式存在资源浪费的可能。因为一个程序员一个对象实例都没有使用,那么饿汉式创建的对象实例就浪费了,懒汉式是使用时才创建,就不存在这个问题。
在我们 JavaSE 标准类中,java.lang.Runtime 就是经典的单例模式
final
final 可以修饰类,属性,方法和局部变量
final的使用场景
当不希望类被继承时,可以用 final 修饰
当不希望父类的某个方法被子类覆盖/重写时,可以用 final 修饰
当不希望某个类的某个属性的值被修改,可以用 final 修饰
当不希望某个局部变量被修改时,可以用 final 修饰
final的注意事项和细节
final 修饰的常量,一般用 XX_XX_XX_XX 来命名
final 修饰的属性在定义时,必须赋值,并且之后不可再修改,赋值可以在如下位置
在定义时 : 如 public final double MAX_VALUE = 10.1;
在构造器中
在代码块中
如果 final 修饰的属性是静态的,则初始化的位置只能是
final 类不能继承,但是可以有实例化对象
如果类不是 final 类,但是含有 final 方法,则该方法虽然不能重写,但是可以被继承
一般来说,如果一个类是 final 类了,就没有必要再将方法,修饰成 final 方法了,
final 不能修饰构造方法(即 构造器)
final 和 static 往往搭配使用,效率更高,底层编译器做了优化处理
包装类(Integer,Double,Float,Boolean 等 都是 final )String 也是 final 类
抽象类
当父类的某些方法,需要声明,但是又不确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 abstract class Animal { private String name; public Animal (String name) { this .name = name; } public abstract void eat () ;
当父类的一些方法不能确定时,可以用 abstract 关键字来修饰该方法,这个方法就是抽象方法,用 abstract 来修饰该类就是抽象类,一般来说,抽象类会被继承,抽象方法会让子类来实现
抽象类介绍
用 abstract 关键字l来修饰一个类,这个类就叫做抽象类
访问修饰符 abstract 类名{ }
用 abstract 关键字来修饰一个方法 时,这个方法就是抽象方法
```java访问修饰符 abstract 返回类型 方法名(参数列表); // 没有方法体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 3. 抽象类的价值更多作用在于设计,是设计者设计好后,让子类继承并实现抽象类 4. 抽象类,是重点,在框架h和设计模式使用较多 #### 使用事项和细节 1. 抽象类不能实例化 2. 抽象类不一定要b包含 abstract 方法,**也就是说 :抽象类可以 没有 abstract 方法** - 一旦类包含了 abstract 方法,**则这个类必须声明为 abstract** 3. abstract 只能修饰 类 和 方法,不能修饰属性和其他的 4. 抽象类可以有任何成员(**抽象类还是类**),比如:非抽象方法,构造器,静态属性 5. 抽象方法,不能有主体,即不能实现 6. 如果一个类继承了 抽象 类,则他必须 实现抽象类 的所有抽象方法,除非 他自己也声明为 abstract 类 **所谓实现,就是要有方法体** 7. 抽象方法不能使用 **private 、final、static **来修饰,因为这些关键字 都是和重写相违背的 ### 接口 --- > **接口就是给出一些没有实现的方法,封装到一起,到某个类要使用时,再根据具体的情况把这些方法写出来** #### 语法 ```java interface 接口名{ // 属性 // 抽象方法 } class 类名 implements 接口 { //自己的属性; //自己的方法; //必须实现的借口的抽象方法 }
小结: 借口是更加抽象的抽象类,抽象类里的方法可以有方法体,接口里的所有方法都没有方法体【jdk 7.0】。接口体现了程序设计的多态和高内聚低耦合的设计思想
特别说明: Jdk 8.0 后接口类可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现
注意事项和细节
接口不能被实例化
接口中的所有方法是 public 方法,接口中抽象方法,可以不用 abstract 修饰
一个普通类实现接口,就必须将该借口的所有方法都实现
抽象类实现接口,可以不用实现接口的方法
一个类同是可以实现多个接口
接口中的属性,只能是 final 的,而且是 public static final 修饰符
接口属性的访问形式 : 接口名.属性名
接口中不能继承其他类 ,但是可以继承多个接口
接口的修饰符,只能是 public 默认 ,这点和类的修饰符是一样的
实现接口 vs 继承类
接口的多态性 多态参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class InterfacePolyParameter { public static void main (String[] args) { IF if01 = new Monster (); if01 = new Car (); AAA a = new BBB (); a = new CCC (); } } interface IF {} class Monster implements IF {} class Car implements IF {} class AAA { } class BBB extends AAA {} class CCC extends AAA {} }
多态数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class InterfacePolyArr { public static void main (String[] args) { Usb[] usbs = new Usb [2 ]; usbs[0 ] = new Phone_ (); usbs[1 ] = new Camera_ (); for (int i = 0 ; i <usbs.length; i++) { sbs[i].work(); if (usbs[i] instanceof Phone_) { ((Phone_) usbs[i]).call(); } } } } interface Usb { void work () ; } class Phone_ implements Usb { public void call () { System.out.println("手机可以打电话..." ); } @Override public void work () { System.out.println("手机工作中..." ); } } class Camera_ implements Usb { @Override public void work () { System.out.println("相机工作中..." ); } }
接口的多态传递现象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class InterfacePolyPass { public static void main (String[] args) { IG ig = new Teacher (); } } interface IH { void hi () ; } interface IG extends IH { }class Teacher implements IG @Override public void hi () { } }
内部类
基本介绍
一个类的内部完整的嵌套了另一个类结构。被嵌套的类成为内部类(inner class) ,嵌套其他类的类统称外部类(outer class) ,是我们类的第五大成员
内部类最大的特点就是 可以直接访问私有属性,并且可以体现类与类之间的包含关系
注意:内部类是学习的难点,同时也是重点,底层源码里有大量内部类
基本语法
1 2 3 4 5 6 7 8 9 class Outer {class Inner {} } class Other {}
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class InnerClass01 { public static void main (String[] args) { } } class Outer { private int n1 = 100 ; public Outer (int n1) { this .n1 = n1; } public void m1 () { System.out.println("m1()" ); } { System.out.println("代码块..." ); } class Inner { } }
内部类的分类
定义在外部类局部位置上(比如方法内):
局部内部类(有类名)
匿名内部类(没有类名,重点)
定义在外部类成员位置上:
成员内部类(没用 static 修饰)
静态内部类(使用 static 修饰)
局部内部类
说明: 局部内部类是定义在外部类的局部位置,比如方法中,并且有类名
可以直接访问外部类 的所有成员 ,包括私有的
不能添加访问修饰符,因为他的地位就是一个局部变量。局部变量是不能使用访问修饰符的。但是可以用 final 修饰, 因为局部变量也可以使用 final
作用域:仅仅在定义它的方法或代码块中
局部内部类 — 访问 — > 外部类的成员[访问方式:直接访问 ]
外部类 — 访问 —> 局部内部类的成员 访问方式: 创建对象再访问(注意:必须在作用域内)
局部内部类定义在方法/代码块中
作用域在方法体,或者代码块中
本质仍然是一个类
外部其他类 — 不能访问 — > 局部内部类(因为局部内部类地位是一个局部变量)
如果外部类和局部内部类的成员重名时 ,默认遵循就近原则 ,如果想访问外部类的成员,则可以使用(外部类.this.成员)去访问
Outer.this 本质就是 外部类的对象 ,即哪个对象调用了方法,Outer.this 就指向哪个对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public class LocalInnerClass { public static void main (String[] args) { Outer02 outer02 = new Outer02 (); outer02.m1(); System.out.println("outer02 的 hashcode=" + outer02); } } class Outer02 { private int n1 = 100 ; private void m2 () { System.out.println("Outer02 m2()" ); } public void m1 () { final class Inner02 { private int n1 = 800 ; public void f1 () { System.out.println("n1=" + n1 + " 外部类的 n1=" + Outer02.this .n1); System.out.println("Outer02.this hashcode=" + Outer02.this ); m2(); } } Inner02 inner02 = new Inner02 (); inner02.f1(); } }
匿名内部类
1.本质是类 2.内部类 3.该类没有名字 4.同时还是对象
说明: 匿名内部类是定义在外部类的局部位置,比如方法中,并且没有名字
基本语法
案例演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 public class AnonymousInnerClass { public static void main (String[] args) { Outer04 outer04 = new Outer04 (); outer04.method(); } } class Outer04 { private int n1 = 10 ; public void method () { IA tiger = new IA () { @Override public void cry () { System.out.println("老虎叫唤..." ); } }; System.out.println("tiger 的运行类型=" + tiger.getClass()); tiger.cry(); tiger.cry(); tiger.cry(); Father father = new Father ("jack" ){ @Override public void test () { System.out.println("匿名内部类重写了 test 方法" ); } }; System.out.println("father 对象的运行类型=" + father.getClass()); father.test(); Animal animal = new Animal (){ @Override void eat () { System.out.println("小狗吃骨头..." ); } }; animal.eat(); } } interface IA { public void cry () ; } class Father { public Father (String name) { System.out.println("接收到 name=" + name); } public void test () { } } abstract class Animal { abstract void eat () ; }
细节
匿名内部类即是一个类的定义 ,同时它本身也是一个对象 ,因此从语法上看,它即有定义类的特征,也有创建对象的特征 ,因此可以调用匿名内部类的方法
可以直接访问外部类的所有成员,包括私有的
不能添加访问修饰符 ,因为他本身就是一个局部变量
作用域 : 仅仅在定义他的方法或代码块中
局部内部类 — 访问 — > 外部类的成员[访问方式:直接访问 ]
外部类 — 访问 —> 局部内部类的成员 访问方式: 创建对象再访问(注意:必须在作用域内)
局部内部类定义在方法/代码块中
作用域在方法体,或者代码块中
本质仍然是一个类
外部其他类 — 不能访问 — > 局部内部类(因为局部内部类地位是一个局部变量)
如果外部类和局部内部类的成员重名时 ,默认遵循就近原则 ,如果想访问外部类的成员,则可以使用(外部类.this.成员)去访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public class AnonymousInnerClassDetail { public static void main (String[] args) { Outer05 outer05 = new Outer05 (); outer05.f1(); System.out.println("main outer05 hashcode=" + outer05); } } class Outer05 { private int n1 = 99 ; public void f1 () { Person p = new Person (){ private int n1 = 88 ; @Override public void hi () { System.out.println("匿名内部类重写了 hi方法 n1=" + n1 + " 外部内的n1=" + Outer05.this .n1 ); System.out.println("Outer05.this hashcode=" + Outer05.this ); } }; p.hi(); } } class Person { public void hi () { System.out.println("Person hi()" ); } public void ok (String str) { System.out.println("Person ok() " + str); } }
实践
当作实参直接传递,简洁高效
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class InnerClassExercise {public static void main (String[] args) { CellPhone cellPhone = new CellPhone (); cellPhone.alarmClock(new Bell () { @Override public void ring () { System.out.println("lalalala" ); } } ); phone phone = new phone (); cellPhone.alarmClock(new phone ()); } public static void f1 (Bell bell) {bell.ring();}} interface Bell {void ring () ;} class CellPhone {public void alarmClock (Bell bell) { bell.ring(); } } class phone implements Bell {@Override public void ring () { System.out.println("wuwuwuwu" ); } }
成员内部类
成员内部类是定义在外部类 的成员位置,并且没有 static 修饰
可以直接访问外部类的所有成员,包括私有的
可以添加任何修饰符(public、protected、默认、private),因为他的地位就是成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class InnerClass { public static void main (String[] args) { Outer outer = new Outer (); Outer.Inner.f2(); outer.f3(); } class Outer { public int a = 10 ; public void f1 () { } class Inner { public void f2 () { System.out.println(a); }} public void f3 () { Inner inner = new Inner (); inner.f2(); } } }
细节
可以直接访问外部类的所有成员,包括私有的
可以添加任何修饰符(public、protected、默认、private),因为他的地位就是成员
作用域 : 是整个类体
成员内部类 — 访问 — 外部类成员 (比如 属性) 【直接访问】
外部类 — 访问 — 内部类 【先创建对象,再访问】
外部其他类 — 访问 — 成员内部类
```java Outer.Inner inner = outer.new Inner();1 2 3 4 5 6 7 8 9 10 11 > 相当于把 new Inner(); 当作outer 的成员 - **在外部类中,编写一个方法,可以返回 Inner对象** ```java public Inner getInnerInstance(){ return new Inner(); }
如果外部类和内部类的成员重名时,内部类访问的话,默认遵守就近原则,如果想访问外部类的成员,则可以使用(外部类.this.成员)去访问
静态内部类
静态内部类s是定义在外部类的成员位置,并且有 static 修饰
细节
可以直接访问外部类的所有静态成员,包括私有的,但不能访问非静态成员
可以添加任意访问修饰符,因为他的地位就是一个成员
作用域 : 同其他成员,为整个类体
静态内部类 — 访问 — 外部类 (比如静态属性) 【访问方式 : 直接访问所有静态成员】
外部类 — 访问 — 静态内部类 【访问方式 : 创建对象,再访问】
外部其他类 — 访问 — 静态内部类
因为静态内部类,是可以通过类名直接访问(前提是满足访问权限)
1 2 Outer.Inner inner = new Outer .Inner(); inner.f1();
通过构造方法来返回一个 Inner 的 对象实例
1 2 3 4 5 public static inner getInnerInstance () { return new Inner (); } Outer.Inner inner = Outer.gerInnerInstance();
如果外部类和静态内部类的成员重名时,静态内部类访问时遵循就近原则,如果想访问外部类的成员,则可以用(外部类.成员)去访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 public class StaticInnerClass { public static void main (String[] args) { Outer10 outer10 = new Outer10 (); outer10.m1(); Outer10.Inner10 inner10 = new Outer10 .Inner10(); inner10.say(); Outer10.Inner10 inner101 = outer10.getInner10(); System.out.println("============" ); inner101.say(); Outer10.Inner10 inner10_ = Outer10.getInner10_(); System.out.println("************" ); inner10_.say(); } } class Outer10 { private int n1 = 10 ; private static String name = "张三" ; private static void cry () {} static class Inner10 { private static String name = "李四" ; public void say () { System.out.println(name + " 外部类name= " + Outer10.name); cry(); } } public void m1 () { Inner10 inner10 = new Inner10 (); inner10.say(); } public Inner10 getInner10 () { return new Inner10 (); } public static Inner10 getInner10_ () { return new Inner10 (); } }
枚举类与注解 枚举类
枚举是一组常量的集合,可以这样理解:枚举属于一种特殊的类,里面只包含一组有限的特定的对象
枚举的实现方式
自定义类实现枚举
不需要提供 setXxx 方法,因为枚举类对象值通常为只读
对枚举对象使用 static + final 共同修饰,实现底层优化
枚举对象名通常使用全部大写,常量的命名规范
枚举对象根据需要,也可以有多个属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public class Enumeration02 { public static void main (String[] args) { System.out.println(Season.AUTUMN); System.out.println(Season.SPRING); } } class Season { private String name; private String desc; public static final Season WINTER = new Season ("冬天" , "寒冷" ); public static final Season AUTUMN = new Season ("秋天" , "凉爽" ); public static final Season SUMMER = new Season ("夏天" , "炎热" ); private Season (String name, String desc) { this .name = name; this .desc = desc; } public String getName () { return name; } public String getDesc () { return desc; } @Override public String toString () { return "Season{" + "name='" + name + '\'' + ", desc='" + desc + '\'' + '}' ; } }
小结:
构造器私有化
本类内部创建一组对象
对外暴露对象(通过为对象 添加 public static final 修饰符)
可以提供 get 方法,但不要提供 set 方法
3.enum 关键字实现枚举 语法
使用关键字 enum 代替 class
public static final class Season SPRING = new Season(“春天”,”温暖”) ; -> SPRING(“春天”,”温暖”);
如果有多个常量(对象),使用 , 隔开即可
如果使用 enum 来实现枚举,要求将定义常量对象,写在前面
注意事项
当我们使用 enum 关键字开发一个枚举类时,默认会继承Enum 类,而且是一个 final 类
传统的 public static final Season SPRING = new Season(”春天”,”温暖”); 简化成SPRING(”春天”,”温暖”),这里必须知道,它调用的是哪个构造器
如果使用无参构造器 创建 枚举对象,则实参列表和小括号都可以省略
当有多个枚举对象时,使用 , 间隔 ,最后有一个分号结尾
枚举对象必须放在枚举类的行首
4. enum 成员方法 1.toString
Enum 类已经重写过了,返回的是当前对象名,子类可以重写该方法,用于返回对象的属性信息
2.name
返回当前对象名(常量名),子类中不能重写
3.ordinal
返回当前对象的位置号,默认从 0 开始
4.values
返回当前枚举类的所有的常量
5.valueOf
将字符串转换成 枚举对象,要求字符串必须为 已有的常量名 ,否则报异常
6.compareTo
比较两个枚举常量,比较的就是位置号
5. enum 实现接口
使用 enum 关键字后,就不能再继承其他类了,因为 enum 会隐式继承 Enum ,而 Java 是单继承机制
枚举类和普通类一样,可以实现接口,形式如下:
1 2 enum 类名 implements 接口1 ,接口2 {}
注解
注解(Annotation)也被成为元数据(MeteData),用于修饰解释 包、类、方法、属性、构造器、局部变量等数据信息
和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息
在 JavaSE 中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE 中 注解占据了重要角色,例如用来配置应用程序的任何切面,代替JavaEE j旧版中所遗留的繁冗代码和 XML 配置
基本的 Annotation 介绍
使用 Annotation 时要在其前面增加 @ 符号,并把该 Annotation 当成一个修饰符使用。用于修饰它支持的程序元素
三个基本的 Annotation @Override
限定某个方法,是重写父类方法,该注解只能用于方法
@Target 是修饰注解的注解,称为元注解
@Deprecated
用于表示某个程序元素(类、方法等)已过时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Deprecated 说明
@SuppressWarnings
抑制编译器警告
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @SuppressWarnings({"rawtypes", "unchecked", "unused"}) public class SuppressWarnings_ {
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public static void main (String[] args) { List list = new ArrayList (); list.add("jack" ); list.add("tom" ); list.add("mary" ); int i; System.out.println(list.get(1 )); } public void f1 () { List list = new ArrayList (); list.add("jack" ); list.add("tom" ); list.add("mary" ); int i; System.out.println(list.get(1 )); } }
元注解
JDK 的元 Annotation 用于修饰其他 Annotation
元注解的种类 Retention
指定注解的作用范围,三种 SOURCE 、CLASS、RUNTIME
Target
指定注解可以在那些地方用
1 2 用于修饰 Annotation 定义,用于指定被修饰的 Annotation 能用于那些程序元素。 @Target 也包含一个名为 value 的成员变量
Documented
指定该注解是否会在 javadoc 体现
1 2 用于指定被该元注解修饰的 Annotation 类 将被 javadoc 工具提取成文档,即在生成文档时,可以看到该注解 说明: 定义为 Documented 的注解必须设置 Retention 为 RUNTIME
Inherited
子类会继承父类注解
1 被它修饰的 Annotation 将具有继承性,如果某个类使用了 被 Inherited 修饰的 Annotation ,则其子类将自动具有该 z
异常
Java 语言中,将程序执行中发生的不正常情况称为“异常”(开发过程中的语法错误和逻辑错误不算异常)
将该代码块 -> 选中 -> ctrl + alt + t -> 选中 try - catch
如果进行了异常处理,那么即使出现了异常,程序可以继续执行
异常事件分类
Error(错误)
Java 虚拟机无法解决的严重问题。 如:JVM 系统内部错误、资源耗尽等严重情况。比如 StackOverFlow [栈溢出]、OOM [out of memory], Error 是严重错误,程序会崩溃
Exception
其他因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如空指针访问,试图读取不存在的文件,网络连接中断,Exception 分为两大类: 运行时异常【程序运行时,发生的异常】,编译时异常【编程时,编译器检查出来的异常】
异常体系图
异常分为两大类 : 运行时异常、编译时异常
运行时异常,编译器检查不出来,编译器不要求强制处置的异常。一般是指编程时的逻辑错误,是程序员应该避免其出现的异常, java.lang.RuntimeException 类及它的子类都是运行时异常
对于运行时异常,可以不做处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响
编译时异常,是编译器要求必须处理的异常
常见五大运行时异常
常见的运行时异常:
NullPointerException 空指针异常
ArithmeticEXception 数学运算异常
ArrayIndexOutOfBoundsException 数组下标越界异常
ClassCastException 类型转换异常
NumberFormatException 数字格式不正确异常
NullPointerException
当应用程序试图在需要对象的地方使用 null 时,抛出该异常
1 2 3 4 5 public static void main (String[] args) { String name = null ; System.out.println(name.length()); }
ArithmeticEXception
当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例
ArrayIndexOutOfBoundsException
用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引
1 2 3 4 5 6 >public static void main (String[] args) { >int [] arr = {1 ,2 ,4 }; >for (int i = 0 ; i <= arr.length; i++) { >System.out.println(arr[i]); >}
ClassCastException
当试图将对象强制转换为不是实例的子类时,抛出该异常
1 2 3 4 5 >public static void main (String[] args) { >A b = new B (); >B b2 = (B)b; >C c2 = (C)b; >}
当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常
1 2 3 4 5 6 7 >public static void main (String[] args) { >String name = "csq" ; > >int num = Integer.parseInt(name); >System.out.println(num); >}
编译异常
编译异常是指在编译期间,就必须处理的异常,否则代码不能通过编译
常见的编译异常 SQLException
操作数据库时,查询表可能发生异常**
IOException
操作文件时,发生的异常
FileNotFoundException
当操作一个不存在的文件时,发生异常
ClassNotFoundException
加载类,而该类不存在时,异常
EOFException
操作文件,到文件末尾,发生异常
lllegalArguementException
参数异常
异常处理
异常处理就是异常发生时,对异常的处理方式
异常处理方式 try - catch - finally
程序员在代码中捕获发生的异常,自行处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 try { }catch (Exception e){ }finally { }
throws
将发生的异常抛出,交给调用者(方法)来处理,最顶级的处理者就是 JVM
try - catch
Java 提供 try 和 catch 块 来处理异常。try 块 用于包含出错的代码。catch 块 用于处理 try 块中发生的异常。可以根据需要在程序中有多个的 try- catch 块
基本语法
1 2 3 4 5 6 7 try {} catch (Exception e) { }
注意事项
如果异常发生了,则异常发 生后面的代码不会执行,直接进入到 catch 块
如果异常没有发生,则顺序执行 try 的代码块,不会进入到 catch
如果希望不管是否发生异常,都执行某段代码(比如关闭连接,释放资源等),则使用 finally
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static void main (String[] args) {try { String str = "csq" ; int a = Integer.parseInt(str); System.out.println("数字:" + a); } catch (NumberFormatException e) { System.out.println("异常信息=" + e.getMessage()); } finally { System.out.println("finally 代码块被执行..." ); } System.out.println("程序继续..." ); }
可以有多个 catch 语句 ,捕获不同的异常 (进行不同的业务处理),要求父类异常在后,子类异常在前 ,比如(Exception 在后,NullPointerException 在前),如果发生异常,只会匹配一个 catch
1 2 3 4 5 6 7 8 9 try { } catch (NullPointerException e){ } catch (Exception e){ } finally { }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public static void main (String[] args) {try { Person person = new Person (); System.out.println(person.getName()); int n1 = 10 ; int n2 = 0 ; int res = n1 / n2; } catch (NullPointerException e) { System.out.println("空指针异常=" + e.getMessage()); } catch (ArithmeticException e) { System.out.println("算术异常=" + e.getMessage()); } catch (Exception e) { System.out.println(e.getMessage()); } finally { } } }
可以进行 try - finally 配合使用,这种用法相当于没有捕获异常 ,因此程序会直接崩掉 ,应用场景:就是执行一段代码,不管是否发生异常,都必须执行某个业务
执行顺序总结
如果没有出现异常,则执行 try 块中所有语句,不执行 catch 语句,如果有finally ,最后还需要执行finally 里面的语句
如果出现异常,则 try 块中异常发生后,try 块剩下的语句不再执行 。将执行 catch 里的语句,如果有 finally ,最后还需要执行 finally 里面的语句
throws
基本介绍
如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显式的声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理
在方法声明中用 throws 语句可以声明抛出异常的列表,throws 后面的异常种类可以是方法中产生的异常类型,也可以是他的父类
代码示例:
1 2 3 4 5 6 7 8 9 public void f2 () throws FileNotFoundException,NullPointerException,ArithmeticException{ FileInputStream fis = new FileInputStream (d: }
细节和注意事项
对于编译异常,程序中必须处理,,比如 try - catch - finally 或者 throws
对于运行时异常,程序中如果没有处理,默认就是 throws 的方式处理
子类重写父类方法时,对于抛出异常的规定 : 子类所抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出的异常的类型的子类型
在 throws 过程中,如果有方法 try - catch - finally ,就相当于处理异常,就不必再 throws
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public class ThrowsDetail { public static void main (String[] args) { f2(); } public static void f2 () { int n1 = 10 ; int n2 = 0 ; double res = n1 / n2; } public static void f1 () throws FileNotFoundException { f3(); } public static void f3 () throws FileNotFoundException { FileInputStream fis = new FileInputStream ("d://aa.txt" ); } public static void f4 () { f5(); } public static void f5 () throws ArithmeticException { } } class Father { public void method () throws RuntimeException { } } class Son extends Father { @Override public void method () throws ArithmeticException { } }
自定义异常
当程序中出现了某些错误,但该错误信息并没有在 Throwable 子类中描述处理,这个时候可以自己设计异常类,用于描述该错误信息
自定义异常步骤
定义类: 自定义异常类名,继承 Exception 或者 RuntimeException
如果继承 Exception ,属于编译异常
如果继承 RuntimeException ,属于运行异常(一般来说,继承 RuntimeException
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class CustomException { public static void main (String[] args) { int age = 180 ; if (!(age >= 18 && age <= 120 )) { throw new AgeException ("年龄需要在 18~120 之间" ); } System.out.println("你的年龄范围正确." ); } } class AgeException extends RuntimeException { public AgeException (String message) { super (message); } }
throw 和 throws 区别
意义
位置
后面跟的东西
throws
异常处理的一种方式
方法声明处
异常类型
throw
手动生成异常对象的关键字
方法体中
异常对象
常用类 包装类
针对八种基本数据类型相应的引用类型 - 包装类
有了类的特点,就可以调用类中的方法
基本数据类型
包装类
boolean
Boolean
char
Character
byte
Byte
short
Short
int
Integer
long
Long
float
Float
double
Double
包装类和基本数据的转换
jdk5 前的手动装箱和拆箱方式,装箱:基本类型 -> 包装类型 ,拆箱反之
jdk5 以后(含 jdk5) 的自动装箱和拆箱方式
自动装箱底层调用的是 valueOf 方法,比如 Integer,valueOf();
其他包装类的应用案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public static void main (String[] args) { int n1 = 100 ; Integer integer = new Integer (n1); Integer integer1 = Integer.valueOf(n1); int i = integer.intValue(); int n2 = 200 ; Integer integer2 = n2; int n3 = integer2; }
包装类型和 String 类型的相互转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public static void main (String[] args) { Integer i = 100 ; String str1 = i + "" ; String str2 = i.toString(); String str3 = String.valueOf(i); String str4 = "12345" ; Integer i2 = Integer.parseInt(str4); Integer i3 = new Integer (str4); System.out.println("ok~~" ); }
Integer 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public static void main (String[] args) { Integer i = new Integer (1 ); Integer j = new Integer (1 ); System.out.println(i == j); Integer m = 1 ; Integer n = 1 ; System.out.println(m == n); Integer x = 128 ; Integer y = 128 ; System.out.println(x == y); }
String 类
String 类的理解和创建对象
String 对象用于保存字符串,也是一组字符序列
字符串常量对象是用双引号括起来的字符序列。例如 “你好” , “12.97” , “boy”
字符串的字符使用的是 **Unicode **字符编码,一个字符(不区分字母还是汉子)占两个字节。
String 类常用构造器
String s1 = new String();
String s2 = new String(String original);
String s3 = new String(char[] a);
String s4 = new String(char[] a,int startIndex,int count);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class String01 { public static void main (String[] args) { String name = "jack" ; name = "tom" ; final char [] value = {'a' ,'b' ,'c' }; char [] v2 = {'t' ,'o' ,'m' }; value[0 ] = 'H' ; }
创建 String 对象的两种方式
方式一 :直接赋值 String s = “csq”;
方式二 : 调用构造器 String s = new String(“csq”);
两种方式的区别:
方式一 : 先从常量池查看是否有 “csq” 数据空间,如果有,直接指向,如果没有则重新创建,然后指向。s 最终指向的是常量池的空间地址
方式二 : 先在堆中创建空间,里面维护了 value 属性,指向常量池的 csq 空间。如果常量池中 没有 csq 则重新创建,如果有直接通过 value 指向,最终指向的是堆中的空间地址。
字符串的特性
Sring 是一个 final 类,代表不可变的字符序列。
字符串是不可变的,一个字符串对象一旦被分配,其内容是不可变的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static void main (String[] args) { String a = "hello" ; String b = "abc" ; String c = a + b; String d = "helloabc" ; System.out.println(c == d); String e = "hello" + "abc" ; System.out.println(d == e); }
重要规则: 底层是 StringBuilder sb = new StringBuilder(); sb.append(a); sb.append(b); sb 是在堆中,并且 append 是在原来字符串的基础上追加的 String c1 = “ab” + “cd”; 常量相加,看的是池。 String c1 = a + b; 变量相加,是在堆中
String 类常用的方法
字符串是保存字符串常量的。每次更新都需要重新开辟空间,效率极低,因此 Java 设计者还提供了 StringBuilder 和 StringBuffer 来增强String 的功能,并提高效率。
常见方法 equals
区分大小写,判断内容是否相等
equalslgnoreCase
忽略大小写的判断内容是否相等
length
获取字符的个数,字符串的长度
indexof
获取字符在字符串中第1次出现的索引,索引从0开始,如果找不到,返回-1
lastIndexOf
获取字符在字符串中最后1次出现的索引,索引从0开始如找不到,返回-1
substring
截取指定范围的子串
trim
去前后空格
charAt
获取某索引处的字符,注意不能使用Str[index]这种方式.
intern
当调用 intern 方法时,如果池中已经包含一个d等于 此 String 对象的字符串(用 equals (object) 方法确定,则返回池中的字符串。否则,将此 String 对象添加到池中,并返回此 String 对象的引用) 此方法最终返回的是 常量池中的地址(对象)
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 public static void main (String[] args) { String s = "heLLo" ; System.out.println(s.toUpperCase()); System.out.println(s.toLowerCase()); String s1 = "宝玉" ; s1 = s1.concat("林黛玉" ).concat("薛宝钗" ).concat("together" ); System.out.println(s1); s1 = "宝玉 and 林黛玉 林黛玉 林黛玉" ; String s11 = s1.replace("宝玉" , "jack" ); System.out.println(s1); System.out.println(s11); String poem = "锄禾日当午,汗滴禾下土,谁知盘中餐,粒粒皆辛苦" ; String[] split = poem.split("," ); poem = "E:\\aaa\\bbb" ; split = poem.split("\\\\" ); System.out.println("==分割后内容===" ); for (int i = 0 ; i < split.length; i++) { System.out.println(split[i]); } s = "happy" ; char [] chs = s.toCharArray(); for (int i = 0 ; i < chs.length; i++) { System.out.println(chs[i]); } String a = "jcck" ; String b = "jack" ; System.out.println(a.compareTo(b)); String name = "john" ; int age = 10 ; double score = 56.857 ; char gender = '男' ; String info = "我的姓名是" + name + "年龄是" + age + ",成绩是" + score + "性别是" + gender + "。希望大家喜欢我!" ; System.out.println(info); String formatStr = "我的姓名是%s 年龄是%d,成绩是%.2f 性别是%c.希望大家喜欢我!" ; String info2 = String.format(formatStr, name, age, score, gender); System.out.println("info2=" + info2); }
StringBuffer 类
java.lang.StringBuffer 代表可变的字符序列,可以对字符串序列内容进行删减。
很多方法与 String 相同,但 StringBuffer 是可变长度的。
StringBuffer 是一个容器。
基础内容
Stringbuffer 是 final 类
实现了 Serializable 接口,可以保存到文件,或文件传输
继承了抽象类 AbstractStringBuider
AbstractStringBuilder 属性 char[] value,存放的字符序列
1 2 3 4 5 6 7 8 9 10 11 public static void main (String[] args) { StringBuffer stringBuffer = new StringBuffer ("hello" ); }
StringBuffer 构造器的使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static void main (String[] args) { StringBuffer stringBuffer = new StringBuffer (); StringBuffer stringBuffer1 = new StringBuffer (100 ); StringBuffer hello = new StringBuffer ("hello" ); }
String 与 StringBuffer
String 保存的是字符串常量,里面的值不能改,每次 String 类 更新实际上就是更换地址,效率较低。
StringBuffer 保存的是字符串变量,里面的值可以更改,每次 StringBuffer 的更新 实际上可以更新内容,不用每次更新地址,效率较高。
String 与 StringBuffer 相互转换
在开发中,我 们经常需要将 String 与 StringBuffer 进行转换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static void main (String[] args) { String str = "hello tom" ; StringBuffer stringBuffer = new StringBuffer (str); StringBuffer stringBuffer1 = new StringBuffer (); stringBuffer1 = stringBuffer1.append(str); StringBuffer stringBuffer3 = new StringBuffer ("韩顺平教育" ); String s = stringBuffer3.toString(); String s1 = new String (stringBuffer3); }
StringBuffer 类常见方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public static void main (String[] args) { StringBuffer s = new StringBuffer ("hello" ); s.append(',' ); s.append("张三丰" ); s.append("赵敏" ).append(100 ).append(true ).append(10.5 ); s.delete(11 , 14 ); System.out.println(s); s.replace(9 , 11 , "周芷若" ); System.out.println(s); int indexOf = s.indexOf("张三丰" ); System.out.println(indexOf); s.insert(9 , "赵敏" ); System.out.println(s); System.out.println(s.length()); System.out.println(s); }
StringBuilder 类
一个可变的字符序列。此类提供一个与 StringBuffer 兼容的 API ,但不保证同步(StringBuilder 不是线程安全)。该类 被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候。如果可能,建议优先采用该类,因为在大多数实现中,它 比 StringBuffer 要快。
在 StringBuiler 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。
1 2 3 4 5 6 7 8 9 10 11 public static void main (String[] args) { StringBuffer stringBuffer = new StringBuffer ("hello" ); }
StringBuffer 类 常用方法
StringBuffer 和 StringBuilder 均代表可变的字符序列,方法是一样的,所以使用和 StringBuffer 一样 。
StringBuilder 是 final
继承了 AbstractStringBuilder 属性 char[] value ,内容存到 value
实现了 Serializable 接口,序列化(所谓序列化即可以保存类型和数据本
1 2 3 4 5 6 7 8 9 10 public static void main (String[] args) { StringBuilder stringBuilder = new StringBuilder (); }
String 与 StringXxx 比较
StringBuilder 和 StringBuffer 非常类似,均代表可变的字符序列,而且方法都一样
String : 不可变字符序列,效率低,但是复用率高
StringBuffer : 可变字符序列,效率较高(增删)、线程安全、看源码
StringBuilder : 可变序列、效率最高、线程不安全
String 使用说明:
String s = “a”; //创建了一个字符串
s += “b”; // 实际上 原来的”a”字符串对象 已经丢弃了,现在又产生了一个字符串 s + “b”(也就是”ab”)。如果多次执行这些改变内容的操作,会导致大量副本字符串对象存留在内存中,将降低效率。如果这样的操作放在循环中,会 极大影响程序的性能 = > 如果我们对 String 做大量修改,不要使用 String
StringXxx 的选择 使用的原则,结论:
如果字符串存在大量的修改操作,一般使用 StringBuffer 或 StringBuiler
如果字符串存在大量的修改操作,并在单线程的情况,使用StringBuilder
如果字符串存在大量的修改操作,并在多线程的情况,使用 StringBuffer
如果我们的字符串存在很少的修改操作,被多个对象引用,使用 String ,比如配置信息
Math 类
Math 类包括用于执行基本数学运算的方法,如初等指数、对数、平方根和三角函数
Math 类 常见方法 .abs [绝对值] 1 2 int abs = Math.abs(-9 );System.out.println(abs);
.pow [求幂] 1 2 double pow = Math.pow(2 , 4 );System.out.println(pow);
.ceil [向上取整] 1 2 double ceil = Math.ceil(3.9 );System.out.println(ceil);
.floor [向下取整] 1 2 double floor = Math.floor(4.001 );System.out.println(floor);
.round [四舍五入] 1 2 3 long round = Math.round(5.51 );System.out.println(round);
.sqrt [开平方] 1 2 double sqrt = Math.sqrt(9.0 );System.out.println(sqrt);
.random [求随机数] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 for (int i = 0 ; i < 100 ; i++) { System.out.println((int )(2 + Math.random() * (7 - 2 + 1 ))); }
.max / min [最大值/最小值] 1 2 3 4 int min = Math.min(1 , 9 );int max = Math.max(45 , 90 );System.out.println("min=" + min); System.out.println("max=" + max);
Arrays 类
Arrays 类中包含了一系列的静态方法,用于管理或操作数组(比如排序和搜索)
Arrays 类常用方法
toString 返回数组的字符串形式 1 System.out.println(Arrays.toString(arr));
sort 排序(自然排序和定制排序)
定制排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 public class ArraysMethod01 { public static void main (String[] args) { Integer[] integers = {1 , 20 , 90 }; Integer arr[] = {1 , -1 , 7 , 0 , 89 }; Arrays.sort(arr, new Comparator () { @Override public int compare (Object o1, Object o2) { Integer i1 = (Integer) o1; Integer i2 = (Integer) o2; return i2 - i1; } }); System.out.println("===排序后===" ); System.out.println(Arrays.toString(arr)); } }
定制排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 public class ArraysSortCustom { public static void main (String[] args) { int [] arr = {1 , -1 , 8 , 0 , 20 }; bubble02(arr, new Comparator () { @Override public int compare (Object o1, Object o2) { int i1 = (Integer) o1; int i2 = (Integer) o2; return i2 - i1; } }); System.out.println("==定制排序后的情况==" ); System.out.println(Arrays.toString(arr)); } public static void bubble01 (int [] arr) { int temp = 0 ; for (int i = 0 ; i < arr.length - 1 ; i++) { for (int j = 0 ; j < arr.length - 1 - i; j++) { if (arr[j] > arr[j + 1 ]) { temp = arr[j]; arr[j] = arr[j + 1 ]; arr[j + 1 ] = temp; } } } } public static void bubble02 (int [] arr, Comparator c) { int temp = 0 ; for (int i = 0 ; i < arr.length - 1 ; i++) { for (int j = 0 ; j < arr.length - 1 - i; j++) { if (c.compare(arr[j], arr[j + 1 ]) > 0 ) { temp = arr[j]; arr[j] = arr[j + 1 ]; arr[j + 1 ] = temp; } } } } }
案例分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 Arrays.sort(books, new Comparator () { @Override public int compare (Object o1, Object o2) { Book book1 = (Book) o1; Book book2 = (Book) o2; return book2.getName().length() - book1.getName().length(); } });
binarySearch
通过二分查找进行查找,要求必须排好序
1 int index = Arrays.binarySearch(arr, 567 );
案例分析 1 2 3 4 5 6 7 8 9 10 public static void main (String[] args) { Integer[] arr = {1 , 2 , 90 , 123 , 567 }; int index = Arrays.binarySearch(arr, 567 ); System.out.println("index=" + index); }
copyOf
数组元素的复制
1 Integer[] newArr = Arrays.copyOf(arr,
1 2 3 4 5 6 7 8 9 Integer[] newArr = Arrays.copyOf(arr, arr.length); System.out.println("==拷贝执行完毕后==" ); System.out.println(Arrays.toString(newArr));
fill
数组元素的填充
1 2 3 4 5 6 7 Integer[] num = new Integer []{9 ,3 ,2 }; Arrays.fill(num, 99 ); System.out.println("==num数组填充后==" ); System.out.println(Arrays.toString(num));
equals
比较两个数组元素内容是否完全一致
1 2 3 4 5 6 7 Integer[] arr2 = {1 , 2 , 90 , 123 }; boolean equals = Arrays.equals(arr, arr2);System.out.println("equals=" + equals);
asList
将一组值,转换成 List
1 2 3 4 5 6 7 8 9 10 List asList = Arrays.asList(2 ,3 ,4 ,5 ,6 ,1 ); System.out.println("asList=" + asList); System.out.println("asList的运行类型" + asList.getClass());
System 类
System 常用方法 exit
退出当前程序
arrayCopy
复制数组元素,比较适合底层调用,一般使用 Arrays.copyOf 完成数组复制
1 2 3 4 5 6 7 8 9 10 11 12 13 System.arraycopy(src, 0 , dest, 0 , src.length);
currentTimeMillens
返回当前时间距离 1970-1-1 的 毫秒数
gc
运行垃圾回收机制 System.gc();
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public static void main (String[] args) { int [] src={1 ,2 ,3 }; int [] dest = new int [3 ]; System.out.println("dest=" + Arrays.toString(dest)); System.out.println(System.currentTimeMillis()); } }
BigInteger 和 BigDecimal 类
BigInteger
适合存放比较大的整型
BigDecimal
适合存放 精度更高的浮点型
常用方法
add 加
substract 减
multiply 乘
divide 除
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public static void main (String[] args) { BigInteger bigInteger = new BigInteger ("23788888899999999999999999999" ); BigInteger bigInteger2 = new BigInteger ("10099999999999999999999999999999999999999999999999999999999999999999999999999999999" ); System.out.println(bigInteger); BigInteger add = bigInteger.add(bigInteger2); System.out.println(add); BigInteger subtract = bigInteger.subtract(bigInteger2); System.out.println(subtract); BigInteger multiply = bigInteger.multiply(bigInteger2); System.out.println(multiply); BigInteger divide = bigInteger.divide(bigInteger2); System.out.println(divide); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public static void main (String[] args) { BigDecimal bigDecimal = new BigDecimal ("1999.11" ); BigDecimal bigDecimal2 = new BigDecimal ("3" ); System.out.println(bigDecimal); System.out.println(bigDecimal.add(bigDecimal2)); System.out.println(bigDecimal.subtract(bigDecimal2)); System.out.println(bigDecimal.multiply(bigDecimal2)); System.out.println(bigDecimal.divide(bigDecimal2, BigDecimal.ROUND_CEILING)); }
日期类
第一代日期类 Date :
精确到毫秒,代表特定的瞬间
格式化和解析日期的具体类,他允许进行格式化(日期 - > 文本)、解析(文本 -> 日期)和规范化
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class Date01 { public static void main (String[] args) throws ParseException { Date d1 = new Date (); System.out.println("当前日期=" + d1); Date d2 = new Date (9234567 ); System.out.println("d2=" + d2); SimpleDateFormat sdf = new SimpleDateFormat ("yyyy 年 MM 月 dd 日 hh:mm:ss E" ); String format = sdf.format(d1); System.out.println("当前日期=" + format); String s = "1996 年 01 月 01 日 10:20:30 星期一" ; Date parse = sdf.parse(s); System.out.println("parse=" + sdf.format(parse)); } }
第二代日期类 基本介绍
1.主要就是 Calendar 类(日历)
1 public abstract class Calendar extends Object implements Serializable ,Cloneable,Comparabale<Calendar>
2.Calendar 类是一个抽象类,它为特定瞬间与一组诸如 YEAR,MONTH,DAY_OF_MONTH,HOUR 等日历字段之间的转换提供了一些方法,并为操作日历字段(例如获得下星期的日期)提供了一些方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public static void main (String[] args) { Calendar c = Calendar.getInstance(); System.out.println("c=" + c); System.out.println("年:" + c.get(Calendar.YEAR)); System.out.println("月:" + (c.get(Calendar.MONTH) + 1 )); System.out.println("日:" + c.get(Calendar.DAY_OF_MONTH)); System.out.println("小时:" + c.get(Calendar.HOUR)); System.out.println("分钟:" + c.get(Calendar.MINUTE)); System.out.println("秒:" + c.get(Calendar.SECOND)); System.out.println(c.get(Calendar.YEAR) + "-" + (c.get(Calendar.MONTH) + 1 ) + "-" + c.get(Calendar.DAY_OF_MONTH) + " " + c.get(Calendar.HOUR_OF_DAY) + ":" + c.get(Calendar.MINUTE) + ":" + c.get(Calendar.SECOND) ); }
第三代日期类: 前面两代日期类的不足分析
JDK 1.0 中包含了一个java.util.Date 类,但是它的大多数方法已经在 JDk 1.1 引入Calendar 类之后被弃用了。而 Calendar 类 存在的问题是:
可变性:
像日期和时间这样的类应该是不可变的
偏移性:
Date 中年份是从 1900 开始的,而月份都从 0 开始
格式化:
格式化只对 Date 有用,Calendar 则不行
提示:
它们也不是线程安全的;不能处理闰秒等(每隔2天,多出 1 s)
LocalDate、LocalTime、LocalDateTime
LocalDate (日期/年月日)、LocalTime(时间/十分秒) 、LocalDateTime(日期时间/年月日时分秒) JDK 8 加入
LocalDate
只包括日期,可以获取日期字段
LocalTime
只包括时间,可以获取时间字段
LocalDateTime
包括日期 + 时间,可以获取日期 + 时间字段
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public static void main (String[] args) { LocalDateTime ldt = LocalDateTime.now(); System.out.println(ldt); DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" ); String format = dateTimeFormatter.format(ldt); System.out.println("格式化的日期=" + format); System.out.println("年=" + ldt.getYear()); System.out.println("月=" + ldt.getMonth()); System.out.println("月=" + ldt.getMonthValue()); System.out.println("日=" + ldt.getDayOfMonth()); System.out.println("时=" + ldt.getHour()); System.out.println("分=" + ldt.getMinute()); System.out.println("秒=" + ldt.getSecond()); LocalDate now = LocalDate.now(); LocalTime now2 = LocalTime.now(); LocalDateTime localDateTime = ldt.plusDays(890 ); System.out.println("890 天后=" + dateTimeFormatter.format(localDateTime)); LocalDateTime localDateTime2 = ldt.minusMinutes(3456 ); System.out.println("3456 分钟前 日期=" + dateTimeFormatter.format(localDateTime2)); }
**类似于 SimpleDateFormat **
1 2 DateTimeFormat dtf = DateTimeformat.ofPattern(格式);String str = dtf.format(日期对象);
Instant 时间戳
类似于 Date ,提供了一系列 和Date 类 转换的方式
1 2 3 4 Date date = Date.from(instant);Instant instant = date.toInstant();
1 2 3 4 5 6 7 8 9 10 11 12 public static void main (String[] args) { Instant now = Instant.now(); System.out.println(now); Date date = Date.from(now); Instant instant = date.toInstant(); }
第三代日期类更多方法
LocalDateTime 类
MonthDay 类 : 检查重复事件
是否是闰年
增加日期的某个部分
使用 plus 方法测试增加时间的某个部分
使用 minus 方法测试查看一年前和一年后的日期
集合
可以动态保存任意多个对象,使用比较方便
提供一系列方便的操作对象的方法:add,remove,set,get
使用集合添加,删除新元素
数组
长度必须一开始指定,而且一旦指定不能更改
保存的必须是同一类型的元素
使用数组进行增加/删除元素的示意代码比较麻烦
集合的框架体系
Java 的集合类很多,主要分为两大类
代码演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public static void main (String[] args) { ArrayList arrayList = new ArrayList (); arrayList.add("jack" ); arrayList.add("tom" ); HashMap hashMap = new HashMap (); hashMap.put("NO1" , "北京" ); hashMap.put("NO2" , "上海" ); }
Collection
Collection 接口和常用方法
Collection 接口实现类的特点
Collection 实现子类可以存放多个元素,每个元素可以是 Object
有些 Collection 的实现类,可以存放重复的元素,有些不可以
有些 Collection 的实现类,有些是有序的(List),有些不是有序(Set)
Collection 接口没有直接实现子类,是通过它的子接口 Set 和 List 来实现的
Collection 接口常用方法 add
添加单个元素
1 2 3 4 list.add("jack" ); list.add(10 ); list.add(true ); System.out.println("list=" + list);
remove
删除指定元素
1 2 3 4 list.remove(true ); System.out.println("list=" + list);
contains
查找元素是否存在
1 2 System.out.println(list.contains("jack" ));
size
获取元素个数
1 2 System.out.println(list.size());
isEmpty
判断是否为空
1 2 System.out.println(list.isEmpty());
clear
清空
1 2 3 list.clear(); System.out.println("list=" + list);
addAll
添加多个元素
1 2 3 4 5 6 ArrayList list2 = new ArrayList ();list2.add("红楼梦" ); list2.add("三国演义" ); list.addAll(list2); System.out.println("list=" + list);
contains
查询多个元素是否存在
1 2 System.out.println(list.containsAll(list2));
removeAll
移除多个元素
1 2 3 4 5 list.add("聊斋" ); list.removeAll(list2); System.out.println("list=" + list);
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 public class CollectionMethod { @SuppressWarnings({"all"}) public static void main (String[] args) { List list = new ArrayList (); list.add("jack" ); list.add(10 ); list.add(true ); System.out.println("list=" + list); list.remove(true ); System.out.println("list=" + list); System.out.println(list.contains("jack" )); System.out.println(list.size()); System.out.println(list.isEmpty()); list.clear(); System.out.println("list=" + list); ArrayList list2 = new ArrayList (); list2.add("红楼梦" ); list2.add("三国演义" ); list.addAll(list2); System.out.println("list=" + list); System.out.println(list.containsAll(list2)); list.add("聊斋" ); list.removeAll(list2); System.out.println("list=" + list); }
Collection 接口遍历元素方式 Iterator遍历 基本介绍
Iterator 对象称为迭代器,主要用于遍历 Collection 集合中的元素
所有实现了 Collection 接口的集合类都有一个 iterator() 方法,用以返回一个实现了 Iterator 接口的对象,即可以返回一个迭代器
Iterator 的结构
Iterator 只用于遍历集合,Iterator 本身并不存放对象
迭代器的执行原理 1 2 3 4 5 6 7 8 Iterator iterator = coll.iterator(); while (iterator.hasNext()){ System.out.println(iterator.next()); }
Iterator 接口的方法 hasNext()
Returns true if the iteration has more elements
next()
Returns the next elements in the iteration
remove()
Removes from the underlying collection the last lelment returned by the iterator (optional operation)
提示
在调用 iterator.next() 方法之前必须要调用 iterator.hasNext() 进行检测,若不调用,且下一条记录无效,直接调用next() 会抛出 NoSuchElementException 异常
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 public class CollectionIterator { @SuppressWarnings({"all"}) public static void main (String[] args) { Collection col = new ArrayList (); col.add(new Book ("三国演义" , "罗贯中" , 10.1 )); col.add(new Book ("小李飞刀" , "古龙" , 5.1 )); col.add(new Book ("红楼梦" , "曹雪芹" , 34.6 )); Iterator iterator = col.iterator(); while (iterator.hasNext()) { Object obj = iterator.next(); System.out.println("obj=" + obj); } iterator = col.iterator(); System.out.println("===第二次遍历===" ); while (iterator.hasNext()) { Object obj = iterator.next(); System.out.println("obj=" + obj); } } } class Book { private String name; private String author; private double price; public Book (String name, String author, double price) { this .name = name; this .author = author; this .price = price; } public String getName () { return name; } public void setName (String name) { this .name = name; } public String getAuthor () { return author; } public void setAuthor (String author) { this .author = author; } public double getPrice () { return price; } public void setPrice (double price) { this .price = price; } @Override public String toString () { return "Book{" + "name='" + name + '\'' + ", author='" + author + '\'' + ", price=" + price + '}' ; } }
for 循环增强遍历
增强 for 循环,可以代替 iterator 迭代器,特点:增强for 就是 简化版的 iterator,本质一样。智能用于遍历集合或数组
基本语法 1 2 3 for (元素类型 元素名 : 集合名或数组名){ }
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 public class CollectionExercise { @SuppressWarnings({"all"}) public static void main (String[] args) { List list = new ArrayList (); list.add(new Dog ("小黑" , 3 )); list.add(new Dog ("大黄" , 100 )); list.add(new Dog ("大壮" , 8 )); for (Object dog : list) { System.out.println("dog=" + dog); } System.out.println("===使用迭代器来遍历===" ); Iterator iterator = list.iterator(); while (iterator.hasNext()) { Object dog = iterator.next(); System.out.println("dog=" + dog); } } } class Dog { private String name; private int age; public Dog (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 String toString () { return "Dog{" + "name='" + name + '\'' + ", age=" + age + '}' ; } }
List
List 接口和常用方法 基本介绍
List 集合类中元素有序(即添加顺序和取出顺序一致)、且可重复
List 集合类中的每个元素都有对应的顺序索引,即支持索引
List 容器中的元素都对应一个整数型的序号,记载其在容器中的位置,可以根据序号存取容器中的元素
JDK API 中List 的实现类有
AbstractList
AbstractSequentialList
ArrayList
AttributeList
CopyOrWriteArrayList
LinkedList
RoleList
RoleUnresolveList
Stack
Vector
List 接口常用方法 add();
void add(int index, Object ele):在index位置插入ele元素
addAll();
boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
get();
Object get(int index):获取指定index位置的元素
indexOf();
int indexOf(Object obj):返回obj在集合中首次出现的位置
lastIndexOf();
int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
remove();
Object remove(int index):移除指定index位置的元素,并返回此元素
set();
Object set(int index, Object ele):设置指定index位置的元素为ele , 相当于是替换.
subList();
List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class ListMethod { @SuppressWarnings({"all"}) public static void main (String[] args) { List list = new ArrayList (); list.add("张三丰" ); list.add("贾宝玉" ); list.add(1 , "csq" ); System.out.println("list=" + list); List list2 = new ArrayList (); list2.add("jack" ); list2.add("tom" ); list.addAll(1 , list2); System.out.println("list=" + list); System.out.println(list.indexOf("tom" )); list.add("csq" ); System.out.println("list=" + list); System.out.println(list.lastIndexOf("csq" )); list.remove(0 ); System.out.println("list=" + list); list.set(1 , "玛丽" ); System.out.println("list=" + list); List returnlist = list.subList(0 , 2 ); System.out.println("returnlist=" + returnlist); } }
List 的遍历元素方式 iterator 迭代器 1 2 3 4 Iterator iter = new col .iterator();while (iter.hasNext()){ Object 0 = iter.next(); }
增强 for 1 2 3 for (Object o : col){ }
普通 for 1 2 3 4 5 for (int i = 0 ;i < list.size();++i){ Object o = list.get(i); System.out.println(object); }
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class ListFor { @SuppressWarnings({"all"}) public static void main (String[] args) { List list = new LinkedList (); list.add("jack" ); list.add("tom" ); list.add("鱼香肉丝" ); list.add("北京烤鸭子" ); Iterator iterator = list.iterator(); while (iterator.hasNext()) { Object obj = iterator.next(); System.out.println(obj); } System.out.println("=====增强for=====" ); for (Object o : list) { System.out.println("o=" + o); } System.out.println("=====普通for====" ); for (int i = 0 ; i < list.size(); i++) { System.out.println("对象=" + list.get(i)); } } }
说明:
使用 LinkedList 完成 使用方式和 ArrayList 一样
ArrayList 底层结构和源码分析 ArrayList 注意事项
permits all elements , including null ,ArrayList 可以加入 null ,并且多个
ArrayList 是由 数组来实现数据存储的
ArrayList 基本等同于 Vector ,除了 ArrayList 是线程不安全的(执行效率高),在多线程情况下,不建议使用 ArrayList
ArrayList 的底层操作机制
ArrayList 中维护了一个Object 类型的数组 elementData
当创建 ArrayList 对象时,如果使用的无参构造器,则初始 elemenData 容量为0,第一次添加,则扩容 elementData 为 10,如需要再次扩容,则扩容 elementData 为 1.5 倍
如果使用的是指定大小的构造器,则初始 elementData 容量为指定大小,如果需要再次扩容,则直接扩容elementData 为 1.5 倍
Vector 底层结构和源码分析 基本介绍
Vector 类的定义说明
1 2 3 public class Vector <E>extends AbstractList <E>implements List <E>,RandomAccess,Cloneable,Serializable
Vector 底层也是一个对象数组, protected Object[] elementData
Vector 是线程同步的,即线程安全,Vector 类的操作方法带有 synchronized
1 2 3 4 5 6 public synchronized E get (int index) { if (index >= elementCount){ throw new ArrayIndexOutOfBoundsException (index); } return elementData; }
在开发中,需要线程同步安全时,考虑使用 Vector
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 @SuppressWarnings({"all"}) public class Vector_ { public static void main (String[] args) { Vector vector = new Vector (8 ); for (int i = 0 ; i < 10 ; i++) { vector.add(i); } vector.add(100 ); System.out.println("vector=" + vector); } }
Vector 和 ArrayList 的比较
底层结构
版本
线程安全(同步)效率
扩容参数
ArrayList
可变数组
JDK1.2
不安全,效率高
如果有参构造 1.5 倍 如果是无参 第一次10 第二次开始 1.5 倍
Vector
可变数组Object[]
Jdk1.0
安全。效率不高
如果是无参,默认10,满后就按 2 倍扩容 如果指定大小,则每次直接按 2 倍 扩容
LinkedList 底层结构 LinkedList 全面说明
LinkedList 底层实现了双向链表 和 双端队列特点
可以添加任意元素(元素可以重复),包括 null
线程不安全,没有实现同步
LinkedList 的底层操作机制
LinkedList 底层维护了一个双向链表
LinedList 中维护了两个属性 first 和 last 分别指向 首节点和尾节点
每个节点(Node对象),里面又维护了 prev,next,item 三个属性,其中通过prev 指向前一个,通过 next 指向下一个,最终实现了双向链表
所以 LinkedList 的元素添加和删除,不是通过数组完成的,相对来说效率较高
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 public class LinkedList01 { public static void main (String[] args) { Node jack = new Node ("jack" ); Node tom = new Node ("tom" ); Node hsp = new Node ("老韩" ); jack.next = tom; tom.next = hsp; hsp.pre = tom; tom.pre = jack; Node first = jack; Node last = hsp; System.out.println("===从头到尾进行遍历===" ); while (true ) { if (first == null ) { break ; } System.out.println(first); first = first.next; } System.out.println("====从尾到头的遍历====" ); while (true ) { if (last == null ) { break ; } System.out.println(last); last = last.pre; } Node smith = new Node ("smith" ); smith.next = hsp; smith.pre = tom; hsp.pre = smith; tom.next = smith; first = jack; System.out.println("===从头到尾进行遍历===" ); while (true ) { if (first == null ) { break ; } System.out.println(first); first = first.next; } last = hsp; System.out.println("====从尾到头的遍历====" ); while (true ) { if (last == null ) { break ; } System.out.println(last); last = last.pre; } } } class Node { public Object item; public Node next; public Node pre; public Node (Object name) { this .item = name; } public String toString () { return "Node name=" + item; } }
LinkedList 的 CRUD 案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 @SuppressWarnings({"all"}) public class LinkedListCRUD { public static void main (String[] args) { LinkedList linkedList = new LinkedList (); linkedList.add(1 ); linkedList.add(2 ); linkedList.add(3 ); System.out.println("linkedList=" + linkedList); linkedList.remove(); System.out.println("linkedList=" + linkedList); linkedList.set(1 , 999 ); System.out.println("linkedList=" + linkedList); Object o = linkedList.get(1 ); System.out.println(o); System.out.println("===LinkeList遍历迭代器====" ); Iterator iterator = linkedList.iterator(); while (iterator.hasNext()) { Object next = iterator.next(); System.out.println("next=" + next); } System.out.println("===LinkeList遍历增强for====" ); for (Object o1 : linkedList) { System.out.println("o1=" + o1); } System.out.println("===LinkeList遍历普通for====" ); for (int i = 0 ; i < linkedList.size(); i++) { System.out.println(linkedList.get(i)); } } }
ArrayList 和 LinkedList 比较
底层结构
增删的效率
改查的效率
ArrayList
可变数组
较低 数组扩容
较高
LinkedList
双向链表
较高,通过链表追加
较低
ArrayList 和 LinkedList 的选择
如果我们 改查 的操作多,选择 ArrayList
如果我们 增删 的操作多,选择 LinkedList
一般来说,在程序中,80%-90%都是查询,因此大部分情况下会选择 ArrayList
在有一个项目中,根据业务灵活选择,也可能这样,一个模块使用的是 ArrayList ,另一个使用的是 LinkedList ,也就是说要根据业务来进行选择
Set
基本介绍
无序(添加和取出的顺序不一致),没有索引
不允许重复元素,所以最多包含有一个 null
JDK API 中 Set 接口的实现类
所有的超级接口
所有已知子接口
所有已知实现类
AbstractSet
ConCurrentSkipListSet
CopyOnWriteArraySet
EnumSet
HashSet
JobStataReasons
LinkedHashSet
TreeSet
Set 接口的常用方法
和 List 接口一样,Set 接口也是 Collection 的子接口,因此常用方法和 Collection 接口也一致
Set 接口的遍历方式
同 Collection 的遍历方式一致,因为 Set 接口是 Collection 接口的子接口
可以使用迭代器
增强 for
不能使用索引的方式来获取
Set 接口实现类 - HashSet HashSet 全面说明
HashSet 实现了 Set 接口
HashSet 实际上是 HashMap
1 2 3 public HashSet () { map = new HashMap <>(); }
可以存放 null 值,但是只能存放一个 null
HashSet 不保证元素是有序的,取决于 hash 后,再确定索引的结果(即 不能保证存放元素的顺序和取出顺序一致)
不能有重复元素/对象,在前面 Set 接口使用有说明
HashSet 底层机制说明
HashSet 底层是HashMap,HashMap底层是(数组,链表,红黑树
HashSet 的添加元素底层实现
先获取元素的 hash 值(hashCode 方法)
对 hash值 进行运算,得出一个索引值 即为要存放在HashSet 中的位置号
如果该位置上没有其他元素则直接存放。
如果该位置上已经有其他元素,则需要进行 equals 判断
如果相等,则不再添加
如果不相等,则以链表的方式添加
过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 @SuppressWarnings({"all"}) public class HashSetSource { public static void main (String[] args) { HashSet hashSet = new HashSet (); hashSet.add("java" ); hashSet.add("php" ); hashSet.add("java" ); System.out.println("set=" + hashSet); } }
HashSet 的扩容 和转成红黑树机制
HashSet 的底层是HashMap,第一次添加时,table 数组扩容到 16,临界值(threshold)是 16 * 加载因子(loadFactor) 是 0.75 = 12
如果 table 数组使用到了临界值 12 ,就会扩容到 16 * 2 = 32,新的临界值就是 32 * 0.75 = 24
在 Java 8 中,如果一条链条的元素个数到达 TREEIFY_THRESHOOLD(默认是 8 ),并且 table 的大小 >= MIN_TREEIFY_CAPACITY(默认 64) ,就会进行树化(红黑树),否则仍然采用数组扩容机制)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 @SuppressWarnings({"all"}) public class HashSetIncrement { public static void main (String[] args) { HashSet hashSet = new HashSet (); for (int i = 1 ; i <= 7 ; i++) { hashSet.add(new A (i)); } for (int i = 1 ; i <= 7 ; i++) { hashSet.add(new B (i)); } } } class B { private int n; public B (int n) { this .n = n; } @Override public int hashCode () { return 200 ; } } class A { private int n; public A (int n) { this .n = n; } @Override public int hashCode () { return 100 ; } }
Set 接口实现类 - LinkedHashSet 基本介绍
LinkedHashSet 是 HashSet 的子类
LinkedHashSet 底层是一个 LinkedHashMap,底层维护了一个数组 + 双向链表
LinkedHashSet 根据元素的 hashCode 值 来决定元素的存储位置。同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的
LinkedHashSet 不允许添加重复元素
说明
在 LinkedHashSet 中维护了一个 hash 表 和 一个双向链表(LinkedHashSet 有 head 和 tail)
每一个节点 有 before 和 after 属性,这样可以形成双向链表
在添加元素时,先求 hash 值,再求索引,确定该元素在 table 中的位置,然后将添加的元素加入到双向链表中(如果已经存在,则不添加)
这样的话,我们遍历 LinkedHashSet 时,也能确保插入顺序和遍历顺序一致
Map
Map 接口实现类的特点
Map 与 Collection 并列存在。用于保存具有映射关系的数据: Key - Value
Map 中的 key 和 value 可以是任何引用类型的数据,会封装到 HashMap$Node 对象中
Map 中的 key 不允许重复,原因和 HashSet 一样
Map 中的 value 可以重复
Map 的 key 可以为 null,value 也可以为 null,注意 key 为 null,只能有一个,value 为 null,可以多个
常用 String 类作为 Map 的 key
key 和 value 之间存在单向 一对一关系,即通过指定的 key 总能找到对应的 value
Map 存放数据的 key - value 示意图,一对 k-v 是放在一个 HashMap$Node 中的,又因为 Node 实现了 Entry 接口,有些书上也说 一对 k - v 就是一个Entry
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @SuppressWarnings({"all"}) public class Map_ { public static void main (String[] args) { Map map = new HashMap (); map.put("no1" , "韩顺平" ); map.put("no2" , "张无忌" ); map.put("no1" , "张三丰" ); map.put("no3" , "张三丰" ); map.put(null , null ); map.put(null , "abc" ); map.put("no4" , null ); map.put("no5" , null ); map.put(1 , "赵敏" ); map.put(new Object (), "金毛狮王" ); System.out.println(map.get("no2" )); System.out.println("map=" + map); } }
Map 接口常用方法 remove
根据键 删除映射关系
get
根据键获取值
1 2 3 Object val = map.get("鹿晗" );System.out.println("val=" + val);
size
获取元素个数
1 2 System.out.println("k-v=" + map.size());
isEmpty
判断个数是否为 0
1 2 System.out.println(map.isEmpty());
clear
清除 k - v
1 2 3 System.out.println("map=" + map);
containsKey
查找键是否存在
1 2 System.out.println("结果=" + map.containsKey("csq" ));
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 @SuppressWarnings({"all"}) public class MapMethod { public static void main (String[] args) { Map map = new HashMap (); map.put("邓超" , new Book ("" , 100 )); map.put("邓超" , "孙俪" ); map.put("王宝强" , "马蓉" ); map.put("宋喆" , "马蓉" ); map.put("刘令博" , null ); map.put(null , "刘亦菲" ); map.put("鹿晗" , "关晓彤" ); map.put("csq" , "csqq" ); System.out.println("map=" + map); map.remove(null ); System.out.println("map=" + map); Object val = map.get("鹿晗" ); System.out.println("val=" + val); System.out.println("k-v=" + map.size()); System.out.println(map.isEmpty()); System.out.println("map=" + map); System.out.println("结果=" + map.containsKey("csq" )); } } class Book { private String name; private int num; public Book (String name, int num) { this .name = name; this .num = num; } }
Map 接口遍历方法
比 List,Set 复杂点,但是基本原理都一样
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 @SuppressWarnings({"all"}) public class MapFor { public static void main (String[] args) { Map map = new HashMap (); map.put("邓超" , "孙俪" ); map.put("王宝强" , "马蓉" ); map.put("宋喆" , "马蓉" ); map.put("刘令博" , null ); map.put(null , "刘亦菲" ); map.put("鹿晗" , "关晓彤" ); Set keyset = map.keySet(); System.out.println("-----第一种方式-------" ); for (Object key : keyset) { System.out.println(key + "-" + map.get(key)); } System.out.println("----第二种方式--------" ); Iterator iterator = keyset.iterator(); while (iterator.hasNext()) { Object key = iterator.next(); System.out.println(key + "-" + map.get(key)); } Collection values = map.values(); System.out.println("---取出所有的value 增强for----" ); for (Object value : values) { System.out.println(value); } System.out.println("---取出所有的value 迭代器----" ); Iterator iterator2 = values.iterator(); while (iterator2.hasNext()) { Object value = iterator2.next(); System.out.println(value); } Set entrySet = map.entrySet(); System.out.println("----使用EntrySet 的 for增强(第3种)----" ); for (Object entry : entrySet) { Map.Entry m = (Map.Entry) entry; System.out.println(m.getKey() + "-" + m.getValue()); } System.out.println("----使用EntrySet 的 迭代器(第4种)----" ); Iterator iterator3 = entrySet.iterator(); while (iterator3.hasNext()) { Object entry = iterator3.next(); Map.Entry m = (Map.Entry) entry; System.out.println(m.getKey() + "-" + m.getValue()); } } }
Map 接口实现类 - HashMap HashMap 小结
Map 接口的常用实现类 : HashMap、HashTable 和 Properties
HashMap 是 Map 接口使用频率最高的实现类
HashMap 是以key - value 对的方式来存储数据(HashMap$Node 类型)
key 不能重复,但是值可以重复,允许使用 null 键 和 null 值
如果添加相同的key ,则会覆盖原来的 key - val ,等同于修改(key 不会替换,val 会替换)
与 HashSet 一样,不保证映射的顺序,因为底层是以 hash 表的方式来存储的。(JDK 8 的hashMap 底层 : 数组 + 链表 + 红黑树)
HashMap 没有实现同步,因此线程是不安全的,方法没有做同步互斥的操作,没有 synchronized
HashMap 底层机制及源码分析
扩容机制
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 @SuppressWarnings({"all"}) public class HashMapSource1 { public static void main (String[] args) { HashMap map = new HashMap (); map.put("java" , 10 ); map.put("php" , 10 ); map.put("java" , 20 ); System.out.println("map=" + map); } }
模拟HashMap 触发扩容、树化情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @SuppressWarnings({"all"}) public class HashMapSource2 { public static void main (String[] args) { HashMap hashMap = new HashMap (); for (int i = 1 ; i <= 12 ; i++) { hashMap.put(i, "hello" ); } hashMap.put("aaa" , "bbb" ); System.out.println("hashMap=" + hashMap); } } class A { private int num; public A (int num) { this .num = num; } @Override public String toString () { return "\nA{" + "num=" + num + '}' ; } }
Map 接口实现类 - HashTable 基本介绍
存放的元素是键值对 : 即 K - V
HashTable 的键和值都不能为 null ,否则会抛出 NullPointerException
HashTable 使用方法和 HashMap 一样
HashTable 是线程安全的(synchronized),hashMap 是线程不安全的
HashMap 和 HashTable 对比
版本
线程安全(同步)
效率
允许null键null值
HashMap
1.2
不安全
高
可以
HashTable
1.0
安全
较低
不可以
Map 接口实现类 - Properties 基本介绍
Properties 类 继承自 HashTable 类 并且实现了 Map 接口,也是使用一种 键值对的形式来保存数据的
他的使用特点和 HashTable 类似
Properties 还可以用于 从 xxx.properties 文件中,加载数据到 Properties 类对象,并进行读取和修改
xxx.properties 文件 通畅作为配置文件
基本使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @SuppressWarnings({"all"}) public class Properties_ { public static void main (String[] args) { Properties properties = new Properties (); properties.put("john" , 100 ); properties.put("lucy" , 100 ); properties.put("lic" , 100 ); properties.put("lic" , 88 ); System.out.println("properties=" + properties); System.out.println(properties.get("lic" )); properties.remove("lic" ); System.out.println("properties=" + properties); properties.put("john" , "约翰" ); System.out.println("properties=" + properties); } }
总结
在开发中,选择什么集合实现类,主要取决于 业务操作点,然后根据集合实现类特性进行选择
开发中如何选择集合实现类
先判断存储的类型(一组对象【单列】 或 一组键值对【双列】)
一组对象【单列】: Collection 接口
允许重复:List
增删多:LinkedList【底层维护了一个双向链表】
改查多:ArrayList 【底层维护 Object 类型的动态数组】
不允许重复:Set
无序:HashSet 【底层是 HashMap,维护了一个哈希表 即:数组+链表+红黑树
排序:TreeSet
插入和取出顺序一致:LinkedHashSet 【维护 数组 + 双向链表】
一组键值对[双列] : Map
键无序:HashMap 【底层是 哈希表 JDK 7 : 数组 + 链表 + 红黑树】
键排序:TreeMap
键插入和取出顺序一致 : LinkedHashMap
读取文件 :Properties
TreeSet代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 @SuppressWarnings({"all"}) public class TreeSet_ { public static void main (String[] args) { TreeSet treeSet = new TreeSet (new Comparator () { @Override public int compare (Object o1, Object o2) { return ((String) o1).length() - ((String) o2).length(); } }); treeSet.add("jack" ); treeSet.add("tom" ); treeSet.add("sp" ); treeSet.add("a" ); treeSet.add("abc" ); System.out.println("treeSet=" + treeSet); } }
TreeMap 代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 @SuppressWarnings({"all"}) public class TreeMap_ { public static void main (String[] args) { TreeMap treeMap = new TreeMap (new Comparator () { @Override public int compare (Object o1, Object o2) { return ((String) o2).length() - ((String) o1).length(); } }); treeMap.put("jack" , "杰克" ); treeMap.put("tom" , "汤姆" ); treeMap.put("kristina" , "克瑞斯提诺" ); treeMap.put("smith" , "斯密斯" ); treeMap.put("hsp" , "韩顺平" ); System.out.println("treemap=" + treeMap); } }
Collections 工具类
工具类介绍
Collections 是一个操作 Set 、List 和 Map 等集合的工具类
Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作
排序操作 reverse(List)
反转 List 中元素的顺序
1 2 Collections.reverse(list);
shuffle(List)
对 List 集合元素进行随机排序
sort(List)
根据元素的自然顺序对指定 List 集合元素按升序排序
1 2 Collections.sort(list);
sort(List,Comparator)
根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
1 2 3 4 5 6 7 8 9 10 11 Collections.sort(list, new Comparator () { @Override public int compare (Object o1, Object o2) {return ((String) o2).length() - ((String) o1).length();} }); System.out.println("字符串长度大小排序=" + list);
swap(List,int,int)
将指定 List 集合中的 i 处 元素 和 j 处 元素进行交换
1 2 3 4 5 Collections.swap(list, 0 , 1 ); System.out.println("交换后的情况" ); System.out.println("list=" + list);
查找、替换操作 Object max(Collection)
根据元素的自然顺序,返回给定集合中的最大元素
1 2 System.out.println("自然顺序最大元素=" + Collections.max(list));
Object max(Collection,Comparator)
根据 Comparator 指定的顺序,返回给定集合中的最大元素
1 2 3 4 5 6 7 8 9 Object maxObject = Collections.max(list, new Comparator () { @Override public int compare (Object o1, Object o2) { return ((String)o1).length() - ((String)o2).length(); } }); System.out.println("长度最大的元素=" + maxObject);
Object min(Collection
根据元素的自然顺序,返回给定集合中的最小元素
Object min(Collection,Comparator)
根据 Comparator 指定的顺序,返回给定集合中的最小元素
int frequency(Collection,Object)
返回指定集合中指定元素的出现次数
1 2 System.out.println("tom 出现的次数=" + Collections.frequency(list, "tom" ));
void copy(List dest,List src)
将 src 中的内容复制到 dest 中
1 2 3 4 5 6 7 8 9 ArrayList dest = new ArrayList ();for (int i = 0 ; i < list.size(); i++) { dest.add("" ); } 拷贝 Collections.copy(dest, list); System.out.println("dest=" + dest);
boolean replaceAll(List list,Onject oldVal,Object newVal)
使用新值替换 List 对象的所有旧值
1 2 3 Collections.replaceAll(list, "tom" , "汤姆" );
泛型
泛型程序设计(Generic programing) 意味着编写的代码可以被很多不同类型的对象所重用
泛型的介绍与好处
泛型的好处
编译时,检查添加元素的类型,提高了安全性
减少了类型转换的次数,提高了效率
不再编译警告
类比
不使用泛型:Dog - 加入 - > Object - 取出 - > Dog //放入到ArrayList 会先转成 Object,在取出时,还需要转换成 Dog
使用泛型:Dog -> Dog -> Dog // 放入时和取出时,不需要类型转换,提高效率
泛型介绍
理解
泛(广泛)型(类型) = > Integer,String….
泛型又称参数化类型,是 JDK 5.0 出现的新特性,解决数据类型的安全性问题
在类声明或实例化时只要指定好需要的具体的类型即可
Java 泛型可以保证如果程序编译时如果没有发出警告,运行时就不会抛出 ClassCastException 异常。同时代码更加简洁,健壮
泛型的作用是:可以在类声明时通过一个标识表示类中某个属性的类型,或者是某个方法的返回值的类型,或者是参数类型
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 public class Generic03 { public static void main (String[] args) { Person<String> person = new Person <String>("韩顺平教育" ); person.show(); Person<Integer> person2 = new Person <Integer>(100 ); person2.show(); } } class Person <E> { E s ; public Person (E s) { this .s = s; } public E f () { return s; } public void show () { System.out.println(s.getClass()); } }
泛型的语法
泛型的声明 1 2 3 interface 接口 <T>{} or class 类 <K,V>{}
说明
其中 T,K,V 不代表值,而是表示类型
任意字母都可以。常用 T 表示,是Type 的缩写
泛型的实例化
要在类名后面指定类型参数的值(类型)。如:
List strList = new ArrayList();
Iterator iterator = customer.iterator();
泛型使用示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 @SuppressWarnings({"all"}) public class GenericExercise { public static void main (String[] args) { HashSet<Student> students = new HashSet <Student>(); students.add(new Student ("jack" , 18 )); students.add(new Student ("tom" , 28 )); students.add(new Student ("mary" , 19 )); for (Student student : students) { System.out.println(student); } HashMap<String, Student> hm = new HashMap <String, Student>(); hm.put("milan" , new Student ("milan" , 38 )); hm.put("smith" , new Student ("smith" , 48 )); hm.put("hsp" , new Student ("hsp" , 28 )); Set<Map.Entry<String, Student>> entries = hm.entrySet(); Iterator<Map.Entry<String, Student>> iterator = entries.iterator(); System.out.println("==============================" ); while (iterator.hasNext()) { Map.Entry<String, Student> next = iterator.next(); System.out.println(next.getKey() + "-" + next.getValue()); } } } class Student { private String name; private int age; 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 String toString () { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}' ; } }
泛型使用的注意事项和细节
interface List{} , public class HashSet{}
在给泛型指定具体类型后,可以传入该类型或其子类类型
泛型使用形式
List list1 = new ArrayList();
List list2 = new ArrayList<>();
如果写成 List list3 = new ArrayList<>(); 默认给它的 泛型是[ E 就是 Object]
代码演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 @SuppressWarnings({"all"}) public class GenericDetail { public static void main (String[] args) { List<Integer> list = new ArrayList <Integer>(); Pig<A> aPig = new Pig <A>(new A ()); aPig.f(); Pig<A> aPig2 = new Pig <A>(new B ()); aPig2.f(); ArrayList<Integer> list1 = new ArrayList <Integer>(); List<Integer> list2 = new ArrayList <Integer>(); ArrayList<Integer> list3 = new ArrayList <>(); List<Integer> list4 = new ArrayList <>(); ArrayList<Pig> pigs = new ArrayList <>(); ArrayList arrayList = new ArrayList (); Tiger tiger = new Tiger (); } } class Tiger <E> { E e; public Tiger () {} public Tiger (E e) { this .e = e; } } class A {}class B extends A {}class Pig <E> { E e; public Pig (E e) { this .e = e; } public void f () { System.out.println(e.getClass()); } }
自定义泛型
基本语法
细节
普通成员可以使用泛型(属性、方法)
使用泛型的数组不能初始化
静态方法中不能使用类的泛型
泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定类型)
如果在创建对象时,没有指定类型,默认为 Object
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 @SuppressWarnings({"all"}) public class CustomGeneric_ { public static void main (String[] args) { Tiger<Double,String,Integer> g = new Tiger <>("john" ); g.setT(10.9 ); System.out.println(g); Tiger g2 = new Tiger ("john~~" ); g2.setT("yy" ); System.out.println("g2=" + g2); } } class Tiger <T, R, M> { String name; R r; M m; T t; T[] ts; public Tiger (String name) { this .name = name; } public Tiger (R r, M m, T t) { this .r = r; this .m = m; this .t = t; } public Tiger (String name, R r, M m, T t) { this .name = name; this .r = r; this .m = m; this .t = t; } public String getName () { return name; } public void setName (String name) { this .name = name; } public R getR () { return r; } public void setR (R r) { this .r = r; } public M getM () { return m; } public void setM (M m) { this .m = m; } public T getT () { return t; } public void setT (T t) { this .t = t; } @Override public String toString () { return "Tiger{" + "name='" + name + '\'' + ", r=" + r + ", m=" + m + ", t=" + t + ", ts=" + Arrays.toString(ts) + '}' ; } }
自定义泛型接口 基本语法 1 2 3 interface 接口名<T,R...>{ }
细节
接口中,静态成员也不能使用泛型(这个和泛型规定一样)
泛型接口的类型,在 继承接口 或者 实现接口 时确定
没有指定类型,默认为 Object
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 public class CustomInterfaceGeneric { public static void main (String[] args) { } } interface IA extends IUsb <String, Double> {} class AA implements IA { @Override public Double get (String s) { return null ; } @Override public void hi (Double aDouble) { } @Override public void run (Double r1, Double r2, String u1, String u2) { } } class BB implements IUsb <Integer, Float> { @Override public Float get (Integer integer) { return null ; } @Override public void hi (Float aFloat) { } @Override public void run (Float r1, Float r2, Integer u1, Integer u2) { } } class CC implements IUsb { @Override public Object get (Object o) { return null ; } @Override public void hi (Object o) { } @Override public void run (Object r1, Object r2, Object u1, Object u2) { } } interface IUsb <U, R> { int n = 10 ; R get (U u) ; void hi (R r) ; void run (R r1, R r2, U u1, U u2) ; default R method (U u) { return null ; } }
自定义泛型方法 1 2 3 4 5 6 7 class Apple <T,R,M>{ public <E>void fly (E e) { System.out.println(e.getClass().getSimpleName()); } public void eat (U u) public void run (M m) {} }
泛型的继承和通配符说明
泛型不具有继承性
> : 支持任意泛型类型
extends A> : 支持 A 类 以及 A 类的子类,规定了泛型的上限
super A> : 支持 A 类 以及 A 类的父类,不限于直接父类,规定了泛型的下限
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 public class GenericExtends { public static void main (String[] args) { Object o = new String ("xx" ); List<Object> list1 = new ArrayList <>(); List<String> list2 = new ArrayList <>(); List<AA> list3 = new ArrayList <>(); List<BB> list4 = new ArrayList <>(); List<CC> list5 = new ArrayList <>(); printCollection1(list1); printCollection1(list2); printCollection1(list3); printCollection1(list4); printCollection1(list5); printCollection2(list3); printCollection2(list4); printCollection2(list5); printCollection3(list1); printCollection3(list3); } public static void printCollection2 (List<? extends AA> c) { for (Object object : c) { System.out.println(object); } } public static void printCollection1 (List<?> c) { for (Object object : c) { System.out.println(object); } } public static void printCollection3 (List<? super AA> c) { for (Object object : c) { System.out.println(object); } } } class AA {} class BB extends AA {} class CC extends BB {}
JUnit
为什么需要 JUit
一个类有很多功能代码需要测试,为了测试,就需要写入到 main 方法中
如果有很多个功能代码测试,就需要来回注释,切换很麻烦
如果直接运行一个方法,就方便很多,并且给出相关信息就好了 -> JUit
基本介绍
JUit 是一个 Java 语言的单元测试框架
多数 Java 的开发环境都已经继承了 JUit 作为单元测试的工具
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class JUnit_ { public static void main (String[] args) { } @Test public void m1 () { System.out.println("m1方法被调用" ); } @Test public void m2 () { System.out.println("m2方法被调用" ); } @Test public void m3 () { System.out.println("m3方法被调用" ); } }
JAVA 事件处理机制 基本说明
java 事件处理是采取 “ 委派事件模型”。当事件发生时,产生事件的对象,会把此 “ 信息” 传递给 “ 事件的监听者 ” 处理,这里所说的 “信息” ,实际上就是 java.awt.event 事件类库里某个类所创建的对象,把它称为 “ 事件的对象”
示意图 事件源 —- 事件/对象 —- 事件监听者【事件处理方法】
事件处理机制深入理解
事件源:事件源就是一个产生事件的对象,比如按钮,窗口等
事件:事件就是承载事件源状态改变时的对象,比如当键盘事件,鼠标事件,窗口事件等等,会生成一个事件对象,该对象保存着当前事件很多信息,比如 KeyEvent 对象有含有被按下键的 code 值。java.awt.event 包 和 javax.swing,event 包中定义类各种事件类型
事件类型:查阅jdk 文档
事件监听器接口
当事件源产生一个事件,可以传送给事件监听者处理
事件监听者实际上就是一个类,该类实现了某个事件监听器接口,比如前面所说的 MyPanle 就是一个类,他实现了 KeyListener 接口,他就可以作为 一个事件监听者,对接收到的事件进行处理
事件监听器接口有很多种,不同的事件监听器接口可以监听不同的事件,一个类可以实现多个监听接口
这些接口在 java.awt.event 包 和 javax.swing.event 包中定义
多线程基础 线程相关概念
线程基本使用
创建线程的两种方式
继承 Thread 类,重写 run 方法
实现 Runnable 接口,重写 run 方法
继承 Thread 类 说明
当一个类继承了 Thread 类, 该类就可以当做线程使用
我们会重写 run 方法, 写上自己的业务代码
run Thread 类 实现了 Runnable 接口的 run 方法
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 public class Thread01 { public static void main (String[] args) throws InterruptedException { Cat cat = new Cat (); cat.start(); System.out.println("主线程继续执行" + Thread.currentThread().getName()); for (int i = 0 ; i < 60 ; i++) { System.out.println("主线程 i=" + i); Thread.sleep(1000 ); } } } class Cat extends Thread { int times = 0 ; @Override public void run () { while (true ) { System.out.println("喵喵, 我是小猫咪" + (++times) + " 线程名=" + Thread.currentThread().getName()); try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } if (times == 80 ) { break ; } } } }
实现 Runnable 接口 说明
java 是单继承的,在某些情况下下一个类可能已经继承了某个父类,这时在用继承 Thread 类方法来创建线程显然已经不可能了
java 设计者们提供了另外一个方式来创建线程,就是通过实现 Runnable 接口来创建线程
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 public class Thread02 { public static void main (String[] args) { Dog dog = new Dog (); Thread thread = new Thread (dog); thread.start(); } } class Animal {} class Tiger extends Animal implements Runnable { @Override public void run () { System.out.println("老虎嗷嗷叫...." ); } } class ThreadProxy implements Runnable { private Runnable target = null ; @Override public void run () { if (target != null ) { target.run(); } } public ThreadProxy (Runnable target) { this .target = target; } public void start () { start0(); } public void start0 () { run(); } } class Dog implements Runnable { int count = 0 ; @Override public void run () { while (true ) { System.out.println("小狗汪汪叫..hi" + (++count) + Thread.currentThread().getName()); try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } if (count == 10 ) { break ; } } } }
继承 Thread 类 和 实现 Runnable 接口的区别
从 java 的设计来看,通过继承 Thread 类 或 实现 Runnable 接口来创建线程本质上没有区别,从 jdk 帮助文档我们可以看到 Thread 类 本身就实现了 Runnable 接口
实现 Runnable 接口方式更加适合多个线程共享一个资源的情况,并且避免了 单继承的限制,建议使用 Runnable
线程终止
基本说明
当线程完成任务后,会自动退出
还可以通过 使用变量 来控制 run 方法退出的方式停止线程,即通知方式
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class ThreadExit_ { public static void main (String[] args) throws InterruptedException { T t1 = new T (); t1.start(); System.out.println("main线程休眠10s..." ); Thread.sleep(10 * 1000 ); t1.setLoop(false ); } } class T extends Thread { private int count = 0 ; private boolean loop = true ; @Override public void run () { while (loop) { try { Thread.sleep(50 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("T 运行中...." + (++count)); } } public void setLoop (boolean loop) { this .loop = loop; } }
线程常用方法
用户线程和守护线程
用户线程:也叫工作线程当线程的任务执行完或通知方式结束
守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
常见的守护线程:垃圾回收机制
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class ThreadMethod03 { public static void main (String[] args) throws InterruptedException { MyDaemonThread myDaemonThread = new MyDaemonThread (); myDaemonThread.setDaemon(true ); myDaemonThread.start(); for ( int i = 1 ; i <= 10 ; i++) { System.out.println("宝强在辛苦的工作..." ); Thread.sleep(1000 ); } } } class MyDaemonThread extends Thread { public void run () { for (; ; ) { try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("马蓉和宋喆快乐聊天,哈哈哈~~~" ); } } }
常用方法第一组 setName
设置线程名称,使之与参数 name 相同
getName
返回该线程的名称
start
使该线程开始执行;Java虚拟机底层调用该线程的 start0 方法
run
调用线程对象的 run 方法
setPriority
更改线程的优先级
getPriority
获取线程的优先级
sleep
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
interrupt
中断线程
注意事项和细节
start 底层会创建新的线程,调用 run ,run 就是一个简单的方法调用,不会启动新的线程
线程的优先级范围
interrupt ,中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠线程
sleep : 线程的静态方法,使当前线程休眠
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class ThreadMethod01 { public static void main (String[] args) throws InterruptedException { T t = new T (); t.setName("csq" ); t.setPriority(Thread.MIN_PRIORITY); t.start(); for (int i = 0 ; i < 5 ; i++) { Thread.sleep(1000 ); System.out.println("hi " + i); } System.out.println(t.getName() + " 线程的优先级 =" + t.getPriority()); t.interrupt(); } } class T extends Thread { @Override public void run () { while (true ) { for (int i = 0 ; i < 100 ; i++) { System.out.println(Thread.currentThread().getName() + " 吃包子~~~~" + i); } try { System.out.println(Thread.currentThread().getName() + " 休眠中~~~" ); Thread.sleep(20000 ); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() + "被 interrupt了" ); } } } }
常用方法第二组 yield
线程的礼让。让出 cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
join
线程的插队。插队的线程一旦成功,则肯定先执行完插入的线程所有的任务
案例 先执行完插入的线程所有的任务 案例:main线程创建一个子线程, 每隔1s输出hello, 输出20次,主线程每隔1秒,输出hi,输出20次.要求: 两个线程同时执行,当主线程输出5次后,就让子线程 运行完毕,主线程再继续,
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class ThreadMethod02 { public static void main (String[] args) throws InterruptedException { T2 t2 = new T2 (); t2.start(); for (int i = 1 ; i <= 20 ; i++) { Thread.sleep(1000 ); System.out.println("主线程(小弟) 吃了 " + i + " 包子" ); if (i == 5 ) { System.out.println("主线程(小弟) 让 子线程(老大) 先吃" ); Thread.yield (); System.out.println("线程(老大) 吃完了 主线程(小弟) 接着吃.." ); } } } } class T2 extends Thread { @Override public void run () { for (int i = 1 ; i <= 20 ; i++) { try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("子线程(老大) 吃了 " + i + " 包子" ); } } }
线程的生命周期
JDK 中用 Thread.State 枚举了几种线程状态
线程状态转换图
线程状态
线程的同步
Synchronized 线程同步机制
在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性
也可以这里理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作
同步具体方法 - Synchronized
同步代码块
syschronized(对象) { //得到对象的锁,才能操作同步代码
//需要被同步代码
}
synchronized 还可以放在 方法声明中,表示整个方法 - 为 同步方法
public synchronized void m (String name){
// 需要被同步代码
}
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 public class SellTicket { public static void main (String[] args) { SellTicket03 sellTicket03 = new SellTicket03 (); new Thread (sellTicket03).start(); new Thread (sellTicket03).start(); new Thread (sellTicket03).start(); } } class SellTicket03 implements Runnable { private int ticketNum = 100 ; private boolean loop = true ; Object object = new Object (); public synchronized static void m1 () { } public static void m2 () { synchronized (SellTicket03.class) { System.out.println("m2" ); } } public void sell () { synchronized ( object) { if (ticketNum <= 0 ) { System.out.println("售票结束..." ); loop = false ; return ; } try { Thread.sleep(50 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数=" + (--ticketNum)); } } @Override public void run () { while (loop) { sell(); } } } class SellTicket01 extends Thread { private static int ticketNum = 100 ; @Override public void run () { while (true ) { if (ticketNum <= 0 ) { System.out.println("售票结束..." ); break ; } try { Thread.sleep(50 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数=" + (--ticketNum)); } } } class SellTicket02 implements Runnable { private int ticketNum = 100 ; @Override public void run () { while (true ) { if (ticketNum <= 0 ) { System.out.println("售票结束..." ); break ; } try { Thread.sleep(50 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数=" + (--ticketNum)); } } }
分析同步原理
互斥锁
基本介绍
Java 语言中,引入了 对象互斥锁的概念,来保证共享数据操作的完整性
每个对象都对应于一个可称为 “ 互斥锁” 的标记,这个标记 用来保证在任一时刻,只能有一个线程访问该对象
关键字 synchronized 来与对象的互斥锁联系。当某个对象用 synchronized 修饰时,表明该对象在任一时刻只能由一个线程访问
同步的局限性:导致程序的执行效率 要降低
同步方法(非静态的)的锁可以是 this ,也可以是其他对象(要求是同一个对象)
同步方法(静态的)的锁为当前类本身
注意事项和细节
同步方法如果没有用 static 修饰:默认锁对象为 this
如果方法使用 static 修饰,,默认锁对象:当前类.class
实现的落地步骤
需要先分析上锁的代码
选择 同步代码块或者 同步方法
要求多个线程的锁对象w为同一个即可
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 public class SellTicket { public static void main (String[] args) { SellTicket03 sellTicket03 = new SellTicket03 (); new Thread (sellTicket03).start(); new Thread (sellTicket03).start(); new Thread (sellTicket03).start(); } } class SellTicket03 implements Runnable { private int ticketNum = 100 ; private boolean loop = true ; Object object = new Object (); public synchronized static void m1 () { } public static void m2 () { synchronized (SellTicket03.class) { System.out.println("m2" ); } } public void sell () { synchronized ( object) { if (ticketNum <= 0 ) { System.out.println("售票结束..." ); loop = false ; return ; } try { Thread.sleep(50 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数=" + (--ticketNum)); } } @Override public void run () { while (loop) { sell(); } } } class SellTicket01 extends Thread { private static int ticketNum = 100 ; @Override public void run () { while (true ) { if (ticketNum <= 0 ) { System.out.println("售票结束..." ); break ; } try { Thread.sleep(50 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数=" + (--ticketNum)); } } } class SellTicket02 implements Runnable { private int ticketNum = 100 ; @Override public void run () { while (true ) { if (ticketNum <= 0 ) { System.out.println("售票结束..." ); break ; } try { Thread.sleep(50 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数=" + (--ticketNum)); } } }
线程的死锁
基本介绍
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程时一定要避免死锁的发生
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 public class DeadLock_ { public static void main (String[] args) { DeadLockDemo A = new DeadLockDemo (true ); A.setName("A线程" ); DeadLockDemo B = new DeadLockDemo (false ); B.setName("B线程" ); A.start(); B.start(); } } class DeadLockDemo extends Thread { static Object o1 = new Object (); static Object o2 = new Object (); boolean flag; public DeadLockDemo (boolean flag) { this .flag = flag; } @Override public void run () { if (flag) { synchronized (o1) { System.out.println(Thread.currentThread().getName() + " 进入1" ); synchronized (o2) { System.out.println(Thread.currentThread().getName() + " 进入2" ); } } } else { synchronized (o2) { System.out.println(Thread.currentThread().getName() + " 进入3" ); synchronized (o1) { System.out.println(Thread.currentThread().getName() + " 进入4" ); } } } } }
释放锁
会释放锁的操作
当前线程的同步方法、同步代码块执行结束 案例:上厕所,完事出来
当前线程在同步代码块、同步方法中遇到break、return。. 案例:没有正常的完事,经理叫他修改bug,不得已出来
当前线程在同步代码块、 同步方法中出现了未处理的Error?或Exception,导致异常结束 案例:没有正常的完事,发现忘带纸,不得已出来
当前线程在同步代码块、同步方法中执行了线程对象的wait0方法,当前线程暂停,并释 放锁。 案例:没有正常完事,觉得需要酝酿下,所以出来等会再进去
不会释放锁的操作
线程执行同步代码块或同步方法时,
程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁
案例:上厕所,太困了,在坑位上眯了一会
线程执行同步代码块时,其他线程调用了该线程的suspend0方法将该线程挂起,该线程不会释放锁。
提示:应尽量避免使用suspend()和resume0来控制线程,方法不再推荐使用
案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 public class Homework02 { public static void main (String[] args) { T t = new T (); Thread thread1 = new Thread (t); thread1.setName("t1" ); Thread thread2 = new Thread (t); thread2.setName("t2" ); thread1.start(); thread2.start(); } } class T implements Runnable { private int money = 10000 ; @Override public void run () { while (true ) { synchronized (this ) { if (money < 1000 ) { System.out.println("余额不足" ); break ; } money -= 1000 ; System.out.println(Thread.currentThread().getName() + " 取出了1000 当前余额=" + money); } try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } } } }
IO 流 文件
什么是文件 文件是保存数据的地方,比如常用的 Word 文档,txt 文档… 都是文件,它即可以保存一张照片,也可以保存视频,音频…
文件流 文件在程序中是以流的方式来操作的
Java 程序(内存)—-(输出流) —-> 文件(磁盘)
文件(磁盘)—-(输入流)—-> Java 程序(内存)
流 数据在数据源(文件)和程序(内存)之间经历的路径
输入流 数据从数据源(文件) 到程序(内存)的路径
输出流 数据从程序(内存)到数据源(文件)的路径
常用的文件操作
创建文件对象相关构造器 和 方法 new File(String pathname)
根据路径构建一个 File 对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void create01 () { String filePath = "e:\\news1.txt" ; File file = new File (filePath); try { file.createNewFile(); System.out.println("文件创建成功" ); } catch (IOException e) { e.printStackTrace(); } }
new File(File parent,String child)
根据父目录文件 + 子路径构建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Test public void create02 () { File parentFile = new File ("e:\\" ); String fileName = "news2.txt" ; File file = new File (parentFile, fileName); try { file.createNewFile(); System.out.println("创建成功~" ); } catch (IOException e) { e.printStackTrace(); } }
new File(String parent,String child)
根据父目录 + 子路径 构建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Test public void create03 () { String parentPath = "e:\\" ; String fileName = "news4.txt" ; File file = new File (parentPath, fileName); try { file.createNewFile(); System.out.println("创建成功~" ); } catch (IOException e) { e.printStackTrace(); } }
获取文件的相关信息 getName getAbsolutePath getParent length exists isFlie isDirectory 目录的操作和文件删除 mkdir mkdirs delete IO 流原理及流的分类
Java IO 流原理
I / O 是 Input / Output 的缩写,I / O 技术是非常实用的技术,用于处理数据传输。如读写文件,网络通讯等
Java 程序中,对于数据的输入 / 输出操作以 “流(Stream)” 的方式进行。
Java.io 包下提供了各种 “流” 类 和接口,用以获取不同种类的数据,并通过方法输入或输出数据
输入 input : 读取外部数据(磁盘、光盘等存储设备的数据) 到 程序(内存)中
输出 output :将程序(内存)中的数据输出到磁盘、光盘等存储设备中
流的分类
按操作数据单位不同分为 :
字节流(8 bit ) 二进制文件
字符流(按字符)文本文件
按数据流的流向不同分为:
按流的角色不同分为:
抽象基类
字节流
字符流
输入流
InputStream
Reader
输出流
OutputStream
Writer
Java 的 IO 流共涉及 40多个类,实际上非常规则,都是从如上 四个抽象基类派生的
由这四个类派生出来的子类名称都是以其父类名作为子类名后缀
IO流体系
Java IO 流
字节流
InputStream
FileStream
PipedInputStream
ObjectInputStream
ByteArrayInputStream
SequenceInputStream
FilterInputStream
BufferedInputStream
DigestInuputStream
DataInputStream
OutputStream
FileOutputStream
PipedOutputStream
ObjectOutputStream
ByteArrayOutputStream
FilterOutputStream
BufferedOutputStream
DigestOutuputStream
DataOutputStream
PrintStream
字符流
Reader
InputStreamReader
BufferedReader
CharArrayReader
FilterReader
PipedReader
StringReader
Writer
OutputStreamWriter
BufferedWriter
CharArrayWriter
FilterWriter
PipedWriter
StringWriter
文件 vs 流 常用类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 public class FileInputStream_ { public static void main (String[] args) { } @Test public void readFile01 () { String filePath = "e:\\hello.txt" ; int readData = 0 ; FileInputStream fileInputStream = null ; try { fileInputStream = new FileInputStream (filePath); while ((readData = fileInputStream.read()) != -1 ) { System.out.print((char )readData); } } catch (IOException e) { e.printStackTrace(); } finally { try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } @Test public void readFile02 () { String filePath = "e:\\hello.txt" ; byte [] buf = new byte [8 ]; int readLen = 0 ; FileInputStream fileInputStream = null ; try { fileInputStream = new FileInputStream (filePath); while ((readLen = fileInputStream.read(buf)) != -1 ) { System.out.print(new String (buf, 0 , readLen)); } } catch (IOException e) { e.printStackTrace(); } finally { try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }
FileOutputStream FileOutputStream 应用实例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class FileOutputStream01 { public static void main (String[] args) { } @Test public void writeFile () { String filePath = "e:\\a.txt" ; FileOutputStream fileOutputStream = null ; try { fileOutputStream = new FileOutputStream (filePath, true ); String str = "hsp,world!" ; fileOutputStream.write(str.getBytes(), 0 , 3 ); } catch (IOException e) { e.printStackTrace(); } finally { try { fileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public class FileCopy { public static void main (String[] args) { String srcFilePath = "e:\\Koala.jpg" ; String destFilePath = "e:\\Koala3.jpg" ; FileInputStream fileInputStream = null ; FileOutputStream fileOutputStream = null ; try { fileInputStream = new FileInputStream (srcFilePath); fileOutputStream = new FileOutputStream (destFilePath); byte [] buf = new byte [1024 ]; int readLen = 0 ; while ((readLen = fileInputStream.read(buf)) != -1 ) { fileOutputStream.write(buf, 0 , readLen); } System.out.println("拷贝ok~" ); } catch (IOException e) { e.printStackTrace(); } finally { try { if (fileInputStream != null ) { fileInputStream.close(); } if (fileOutputStream != null ) { fileOutputStream.close(); } } catch (IOException e) { e.printStackTrace(); } } } }
FlieReader 相关方法 new FileReader(File/String) read
每次读取单个字符,返回该字符,如果到文件末尾返回 -1
read(char[])
批量读取多个字符到数组,返回读取到的字符数,如果到文件末尾返回 -1
相关 API
new String (char[]) : 将 char[] 转换成 String
new String (char[],off,len) : 将 char[] 的指定部分转换成 String
FileWriter 常用方法 new FileWriter(File / String)
覆盖模式,相当于流的指针在首端
new FileWriter(File/String,true)
追加模式,相当于流的指针在尾端
write(int)
写入单个字符
write(char[])
写入指定数组
write(char[],off,len)
写入指定数组的指定部分
write(String)
写入整个字符串
write( String,off,len)
写入字符串的指定部分
相关 API
String 类:toCharArray : 将String 转换成 char[]
注意事项 FileWriter 使用后,必须要关闭(close)或刷新(flush),否则写入不到指定的文件
FileReader 和 FileWriter 应用案例 使用 FileReader 从 story.txt 读取内容, 并显示 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 public class FileReader_ { public static void main (String[] args) { } @Test public void readFile01 () { String filePath = "e:\\story.txt" ; FileReader fileReader = null ; int data = 0 ; try { fileReader = new FileReader (filePath); while ((data = fileReader.read()) != -1 ) { System.out.print((char ) data); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (fileReader != null ) { fileReader.close(); } } catch (IOException e) { e.printStackTrace(); } } } @Test public void readFile02 () { System.out.println("~~~readFile02 ~~~" ); String filePath = "e:\\story.txt" ; FileReader fileReader = null ; int readLen = 0 ; char [] buf = new char [8 ]; try { fileReader = new FileReader (filePath); while ((readLen = fileReader.read(buf)) != -1 ) { System.out.print(new String (buf, 0 , readLen)); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (fileReader != null ) { fileReader.close(); } } catch (IOException e) { e.printStackTrace(); } } } }
使用 FileWriter 将 “风雨之后, 定见彩虹” 写入到 note.txt 文件中 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 public class FileWriter_ { public static void main (String[] args) { String filePath = "e:\\note.txt" ; FileWriter fileWriter = null ; char [] chars = {'a' , 'b' , 'c' }; try { fileWriter = new FileWriter (filePath); fileWriter.write('H' ); fileWriter.write(chars); fileWriter.write("韩顺平教育" .toCharArray(), 0 , 3 ); fileWriter.write(" 你好北京~" ); fileWriter.write("风雨之后,定见彩虹" ); fileWriter.write("上海天津" , 0 , 2 ); } catch (IOException e) { e.printStackTrace(); } finally { try { fileWriter.close(); } catch (IOException e) { e.printStackTrace(); } } System.out.println("程序结束..." ); } }
节点流和处理流
基本介绍
节点流k可以从一个特定的数据源读写数据
处理流(也叫包装流)是 “连接” 在已存在的流 (节点流或处理流)之上,为程序提供更为强大的读写功能,也更加灵活
节点流和处理流的区别和联系
节点流是底层流/低级流,直接与数据源连接
处理流(包装流)包装节点流,既可以消除不同节点流的实现差异,也可以 提供更方便的方法来完成输入输出
处理流(包装流)对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连
处理流的主要功能
性能的提高:主要以增加缓冲的方式来提高输入输出的效率
操作的便捷:处理流可能提供了一系列便捷的方法来一次输入输出大批量的数据,使用更加灵活方便
处理流 BufferedReader 和 BufferedWriter
BufferedReader 和 BufferedWriter 属于字节流,是按照字符来读取数据的
关闭处理流时,只需要关闭外层流即可
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class BufferedReader_ { public static void main (String[] args) throws Exception { String filePath = "e:\\a.java" ; BufferedReader bufferedReader = new BufferedReader (new FileReader (filePath)); String line; while ((line = bufferedReader.readLine()) != null ) { System.out.println(line); } bufferedReader.close(); } }
BufferedWriter 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class BufferedWriter_ { public static void main (String[] args) throws IOException { String filePath = "e:\\ok.txt" ; BufferedWriter bufferedWriter = new BufferedWriter (new FileWriter (filePath)); bufferedWriter.write("hello, csq!" ); bufferedWriter.newLine(); bufferedWriter.write("hello2, csqq!" ); bufferedWriter.newLine(); bufferedWriter.write("hello3, csqqq!" ); bufferedWriter.newLine(); bufferedWriter.close(); } }
BufferedReader 和 BufferedWriter 完成文本文件拷贝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public class BufferedCopy_ { public static void main (String[] args) { String srcFilePath = "e:\\a.java" ; String destFilePath = "e:\\a2.java" ; BufferedReader br = null ; BufferedWriter bw = null ; String line; try { br = new BufferedReader (new FileReader (srcFilePath)); bw = new BufferedWriter (new FileWriter (destFilePath)); while ((line = br.readLine()) != null ) { bw.write(line); bw.newLine(); } System.out.println("拷贝完毕..." ); } catch (IOException e) { e.printStackTrace(); } finally { try { if (br != null ) { br.close(); } if (bw != null ) { bw.close(); } } catch (IOException e) { e.printStackTrace(); } } } }
BufferedInputStream 是字节流,在创建 BufferedInputStream 时,会创建一个内部缓存区数组
介绍 BufferedOutputStream BufferedOutputStream 是字节流,实现缓冲的输出流,可以将多个字符写入调用底层系统
图片/音乐的 拷贝 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 public class BufferedCopy02 { public static void main (String[] args) { String srcFilePath = "e:\\a.java" ; String destFilePath = "e:\\a3.java" ; BufferedInputStream bis = null ; BufferedOutputStream bos = null ; try { bis = new BufferedInputStream (new FileInputStream (srcFilePath)); bos = new BufferedOutputStream (new FileOutputStream (destFilePath)); byte [] buff = new byte [1024 ]; int readLen = 0 ; while ((readLen = bis.read(buff)) != -1 ) { bos.write(buff, 0 , readLen); } System.out.println("文件拷贝完毕~~~" ); } catch (IOException e) { e.printStackTrace(); } finally { try { if (bis != null ) { bis.close(); } if (bos != null ) { bos.close(); } } catch (IOException e) { e.printStackTrace(); } } } }
看一个需求
1.将int num=100这个int数据保存到文件中,注意不是100数字,而是int100,并且,能够 从文件中直接恢复int100
2.将Dogdog=new Dog(“小黄”,3)这个dog对象保存到文件中,并且能够从文件恢复
3.上面的要求,就是能够将基本数据类型或者对象进行序列化和反序列化操作
序列化和反序列化
序列化就是在保存数据时,保存数据的值和数据类型
反序列化就是在恢复数据时,恢复数据的值和数据类型
需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是序列化的,该类必须实现如下两个接口之一
Serializable 这是一个标记接口,没有方法
Externalizable 该接口有方法需要实现,因此我们一般实现上面的 Serializable 接口
对象流介绍 提供了 对基本类型或对象类型的序列化和反序列化方法
ObjectInputStream : 提供 序列化功能
ObjectOutputStream:提供反序列化功能
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class ObjectOutStream_ { public static void main (String[] args) throws Exception { String filePath = "e:\\data.dat" ; ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream (filePath)); oos.writeInt(100 ); oos.writeBoolean(true ); oos.writeChar('a' ); oos.writeDouble(9.5 ); oos.writeUTF("韩顺平教育" ); oos.writeObject(new Dog ("旺财" , 10 , "日本" , "白色" )); oos.close(); System.out.println("数据保存完毕(序列化形式)" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 1. 创建流对象ObjectInputStream ois = new ObjectInputStream (new FileInputStream ("src\\data.dat" ));System.out.println(ois.readInt()); System.out.println(ois.readBoolean()); System.out.println(ois.readChar()); System.out.println(ois.readDouble()); System.out.println(ois.readUTF()); System.out.println(ois.readObject()); System.out.println(ois.readObject()); System.out.println(ois.readObject()); ois.close(); System.out.println("以反序列化的方式读取(恢复)ok~" );
注意事项和细节
读写顺序要一致
要求序列化或反序列化对象,需要实现 Serializable 接口
序列化的类中,建议添加 SerialVersionUID ,为了提高版本的兼容性
序列化对象时,默认将里面所有的属性都序列化,但除了 static 和 transient 修饰的成员
序列化对象时,要求里面属性的类型也需要实现序列化接口
序列化具备可继承性,也就是说如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化
标准输入输出流 介绍
System.in 标准输入 类型 InputStream 默认设备 键盘
System.out 标准输出 类型 OutputStream 默认设备 显示器
InputStreamReader : Reader的子类,可以将InputStream(字节流)包 装成(转换)Reader(字符流)
OutputStreamWriter::Writer的子类,实现将OutputStream(字节流) 包装成Writer(字符流)
当处理纯文本数据时,如果使用字符流效率更高,并且可以有效解决中文 问题,所以建议将字节流转换成字符流
可以在使用时指定编码格式(比如utf-8,gbk,gb2312,IS08859-1等)
应用案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class InputStreamReader_ { public static void main (String[] args) throws IOException { String filePath = "e:\\a.txt" ; BufferedReader br = new BufferedReader (new InputStreamReader ( new FileInputStream (filePath), "gbk" )); String s = br.readLine(); System.out.println("读取内容=" + s); br.close(); } }
打印流-PrintStream 和 PrintWriter
打印流只有输出流,没有输入流
应用案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class PrintStream_ { public static void main (String[] args) throws IOException { PrintStream out = System.out; out.print("john, hello" ); out.write("韩顺平,你好" .getBytes()); out.close(); System.setOut(new PrintStream ("e:\\f1.txt" )); System.out.println("hello, 韩顺平教育~" ); } }
Properties 类
如下一个配置文件mysql..properties ip=192.168.0.13 user=root pwd=12345 请问编程读取ip、user和pwd的值是多少
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Properties01 { public static void main (String[] args) throws IOException { BufferedReader br = new BufferedReader (new FileReader ("src\\mysql.properties" )); String line = "" ; while ((line = br.readLine()) != null ) { String[] split = line.split("=" ); if ("ip" .equals(split[0 ])) { System.out.println(split[0 ] + "值是: " + split[1 ]); } } br.close(); } }
基本介绍
Properties 的常见方法 load
加载配置文件的键值对到 Properties 对象
list
将数据显示到指定设备
getProperty(key)
根据键获取值
setProperty(key,value)
设置键值对到 Properties 对象
store
将 Properties 中的键值对 存储到配置文件,在 idea 中,保存信息到配置文件,如果含有中文,会存储为 unicode 码
应用案例
使用Properties类完成对mysql..properties的读取
使用Properties类添加key-val到新文件mysql2.properties中
使用Properties类完成对mysql2.properties的读取,并修改某个key-val
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Properties02 { public static void main (String[] args) throws IOException { Properties properties = new Properties (); properties.load(new FileReader ("src\\mysql.properties" )); properties.list(System.out); String user = properties.getProperty("user" ); String pwd = properties.getProperty("pwd" ); System.out.println("用户名=" + user); System.out.println("密码是=" + pwd); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 public class Properties03 { public static void main (String[] args) throws IOException { Properties properties = new Properties (); properties.setProperty("charset" , "utf8" ); properties.setProperty("user" , "汤姆" ); properties.setProperty("pwd" , "888888" ); properties.store(new FileOutputStream ("src\\mysql2.properties" ), null ); System.out.println("保存配置文件成功~" ); } }
网络编程 网络的相关概念
网络通信 概念 两台设备之前通过网络实现数据传输
网络通信 将-数据通过网络从一台设备传输到另一台设备
Java.net 包下提供了一系列的类或接口,供程序员使用,完成网络通信
网络 概念 两台或多台设备通过一定物理设备连接起来构成了网络
网络分类
局域网:覆盖范围最小,仅仅覆盖了一个教师或一个机房
城域网:覆盖范围较大,可以覆盖一整个城市
广域网:覆盖范围最大,可以覆盖全国,甚至全球,万维网是广域网的代表
ip地址 概念 用于唯一标识网络中 的每台计算机/主机
注意事项和细节
查看ip 地址 :ipconfig
ip 的表示形式:点分十进制 xx.xx.xx.xx
每一个十进制的范围 0 ~ 255
ip 地址的组成 = 网络地址 + 主机地址 。比如 192.168.16.69
IPv6 是互联网工程任务组设计的用于代替 IPv4 的下一代协议,其地址数量号称可以 为全世界每一粒沙子编上一个地址
由于IPv4 最大的问题在于网络地址资源有限,严重制约了互联网的应用与发展。IPv6 的使用,不仅能解决网络地址资源数量的问题,而且也解决了 多种接入设备连入互联网的障碍
IP 地址分类
域名
www.4399.com
好处 为了方便记忆,解决记 ip 的困难
概念 将 IP 地址映射成 域名
端口号 概念 用于标识计算机上 某个特定的网络程序
表示形式 以整数形式,端口范围 0 ~ 65535 [2 个 字节表示端口 0 ~ 2^16 - 1]
注意事项和细节
0 ~ 1024 已经被占用,比如 ssh 22,ftp 21 , smtp 25 http 80
常见的网络程序端口号
tomcat:8080.
mysql : 3306
oracle : 1521
sqlserver : 1433
网络通信协议
协议(TCP/IP) TCP / IP (Transmission Control Protocol / Internet Protocol) 的简写
中文译名为 传输控制协议 / 因特网互联协议,又叫网络通讯协议,这个协议是 Internet 最基本的协议、Internet 国际互联网络的基础,简单的说,就是由网络层的 IP 协议 和传输层的 TCP 协议组成的
OSI 模型
TCP/IP 模型
TCP/IP模型各层对应协议
应用层
应用层
HTTP、ftp、telnet、DNS…
表示层
应用层
HTTP、ftp、telnet、DNS…
会话层
应用层
HTTP、ftp、telnet、DNS…
传输层
传输层(TCP)
TCP、UDP…
网络层
网络层(IP)
IP、ICMP、ARP…
数据链路层
物理 + 数据链路层
Link
物理层
物理 + 数据链路层
Link
TCP/UDP TCP 协议 :传输控制协议
使用 TCP 协议之前,须先建立 TCP 连接,形成传输数据通道
传输前,采用 “三次握手” 方式,是可靠的
TCP 协议进行通信的两个应用进程 : 客户端 、服务端
在连接中可进行大数据量的传输
传输完毕,需释放已建立的连接,效率低
UDP 协议 : 用户数据协议
将数据、源、目的封装成数据包,不需要建立连接
每个数据包的大小限制在 64k 内,不适合传输大量数据
因无需连接,故是不可靠的
发送数据结束时无需释放资源(因为不是面向连接的),速度快
举例:发短信
InetAddress 类
相关方法 getLocalHost
获取本机 InetAddress 对象
getByName
根据指定主机名/域名获取 IP 地址对象
getHostName
获取 InetAddress 对象的主机名
getHostAddress
获取 InetAddress 对象的地址
应用案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class API_ { public static void main (String[] args) throws UnknownHostException { InetAddress localHost = InetAddress.getLocalHost(); System.out.println(localHost); InetAddress host1 = InetAddress.getByName("DESKTOP-S4MP84S" ); System.out.println("host1=" + host1); InetAddress host2 = InetAddress.getByName("www.baidu.com" ); System.out.println("host2=" + host2); String hostAddress = host2.getHostAddress(); System.out.println("host2 对应的ip = " + hostAddress); String hostName = host2.getHostName(); System.out.println("host2对应的主机名/域名=" + hostName); } }
Socket
基本介绍
套接字(Socket) 开发网络应用程序被广泛应用,以至于成为事实上的标准
通信的两端都要有 Socket, 是两台机器间通信的端点
网络通信其实就是 Socket 间的通信
Socket 允许程序把网络连接当成一个流,数据在两个 Socket 间通过 IO 传输
一般主动发起通信的应用属客户端,等待通信请求的是服务端
TCP 网络通信编程
基本介绍
基于 客户端 — 服务端的网络通信
底层使用的是 TCP / IP 协议
应用场景举例:客户端发送数据,服务端接受并显示控制台
基于 Socket 的 TCP 编程
反射 反射机制
Java Reflection
反射机制允许程序在执行期j借助于 Reflection API 取得任何类的内部信息(比如成员变量、构造器、成员方法等),并能操作对象的属性及方法。反射在设计模式和框架底层都能用得到。
加载完类之后,在堆中就产生了一个 Class 类型的d对象(一个类只有一个 Class 对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个 Class 对象就像一面镜子,透过这个镜子看到 类的结构,所以形象的称之为 : 反射
Java 反射机制原理示意图
Java 反射机制可完成的操作
在运行时判断r任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时得到任意一个类所具有的成员变量和方法
在运行时调用任意一个对象的成员变量和方法
生成动态代理
反射相关的主要类
Java.lang.Class :代表一个类,Class 对象表示某个类加载后在堆中的对象
java.lang.reflect.Method : 代表类的方法,Method 对象表示某个类的方法
java.lang.reflect.Field :代表类的成员变量,Field 对象表示某个类的成员变量
java.lang.reflect.Constructor : 代表类的构造方法,Constructor 对象表示构造器
这些类都在 java.lang.reflect
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class Reflection01 { public static void main (String[] args) throws Exception { Properties properties = new Properties (); properties.load(new FileInputStream ("src\\re.properties" )); String classfullpath = properties.get("classfullpath" ).toString(); String methodName = properties.get("method" ).toString(); Class cls = Class.forName(classfullpath); Object o = cls.newInstance(); System.out.println("o的运行类型=" + o.getClass()); Method method1 = cls.getMethod(methodName); System.out.println("=============================" ); method1.invoke(o); Field nameField = cls.getField("age" ); System.out.println(nameField.get(o)); Constructor constructor = cls.getConstructor(); System.out.println(constructor); Constructor constructor2 = cls.getConstructor(String.class); System.out.println(constructor2); } }
反射优点和缺点 优点 可以动态的创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑
缺点 使用反射机制基本是解释执行,对执行速率有影响
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 public class Reflection02 { public static void main (String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { m1(); m2(); m3(); } public static void m1 () { Cat cat = new Cat (); long start = System.currentTimeMillis(); for (int i = 0 ; i < 90 ; i++) { cat.hi(); } long end = System.currentTimeMillis(); System.out.println("m1() 耗时=" + (end - start)); } public static void m2 () throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { Class cls = Class.forName("com.hspedu.Cat" ); Object o = cls.newInstance(); Method hi = cls.getMethod("hi" ); long start = System.currentTimeMillis(); for (int i = 0 ; i < 900000000 ; i++) { hi.invoke(o); } long end = System.currentTimeMillis(); System.out.println("m2() 耗时=" + (end - start)); } public static void m3 () throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { Class cls = Class.forName("com.hspedu.Cat" ); Object o = cls.newInstance(); Method hi = cls.getMethod("hi" ); hi.setAccessible(true ); long start = System.currentTimeMillis(); for (int i = 0 ; i < 900000000 ; i++) { hi.invoke(o); } long end = System.currentTimeMillis(); System.out.println("m3() 耗时=" + (end - start)); } }
反射调用优化 - 关闭访问检查
Method 和 Field 、Constructor 对象 都有 setAccessible() 方法
setAccessible 作用是启动和禁用访问安全检查的开关
参数值为 true 表示反射的对象在使用时取消访问安全检查,提高反射的效率。参数值为 false 则表示 反射的对象执行访问安全检查
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class Class01 { public static void main (String[] args) throws ClassNotFoundException { Class cls1 = Class.forName("com.hspedu.Cat" ); Class cls2 = Class.forName("com.hspedu.Cat" ); System.out.println(cls1.hashCode()); System.out.println(cls2.hashCode()); Class cls3 = Class.forName("com.hspedu.Dog" ); System.out.println(cls3.hashCode()); } }
Class 类
基本介绍
Class 也是类,因此也继承 Object 类
Class 类对象不是 new出来的,而是系统创建的
对于某个类的 Class 类对象,在内存中只有一份,因为类只加载一次
每个类的实例都会记得自己是由哪个 Class 实例所生成
通过 Class 对象可以完整的得到一个类的完整结构,通过一系列 API
Class 对象是存放在堆的
类的字节码二进制,是放在方法区的,有的地方称为类的元数据(包括方法代码,变量名,方法名,访问权限等等)
Class 类常用方法 static Class forName(String name) 返回指定类名name的Class对象
Object newlnstance() 调用缺省构造函数,返回该Class对象的一个实例
getName() 返回此Cass对象所表示的实体(类、接口、数组类、基本类型等)名称
Class [] getInterfaces() 获取当前Class对象的接口
ClassLoader getClassLoader() 返回该类的类加载器
Class getSuperclass() 返回表示此Classl所表示的实体的超类的Class
Constructor[]getConstructors() 返回一个包含某些Constructori对象的数组
Field[]getDeclaredFields() 返回Field对象的一个数组
Method getMethod 返回一个Methodi对象,此对象的形参类型为paramType
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class Class02 { public static void main (String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException { String classAllPath = "com.hspedu.Car" ; Class<?> cls = Class.forName(classAllPath); System.out.println(cls); System.out.println(cls.getClass()); System.out.println(cls.getPackage().getName()); System.out.println(cls.getName()); Car car = (Car) cls.newInstance(); System.out.println(car); Field brand = cls.getField("brand" ); System.out.println(brand.get(car)); brand.set(car, "奔驰" ); System.out.println(brand.get(car)); System.out.println("=======所有的字段属性====" ); Field[] fields = cls.getFields(); for (Field f : fields) { System.out.println(f.getName()); } } }
获取 Class 类对象
前提 : 已知一个类的全类名,且该类在类路径下,可通过 Class 类的静态方法 forName() 获取,可能抛出 ClassNotFoundException ,实例 : Class cls1 = Class.forName(“java.lang.cat”)
应用场景:多用于配置文件,读取类全路径,加载类
前提:已知某个类的实例,调用该实例的 getClass() 方法获取 Class 对象,实例:Class cls3 = 对象.getClass(); //运行类型
应用场景:通过创建好的对象,获取 Class 对象
前提:若已知具体的类,通过类的Class 获取,该方式 最为安全可靠,程序性能最高。实例:Class cls2 = Cat.class;
应用场景 : 多用于参数传递,比如通过反射得到对应构造器对象
其他方法:
ClassLoader cl = 对象.getClass().getClassLoader();
Class cl4 = cl.loadClass(“类的全名”);
基本数据类型(int,char,boolean,float,double,byte,short,long) 按如下方式得到 Class类 对象
基本数据类型对应的包装类,可以通过 .TYPE 得到 Class 类 对象
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 public class GetClass_ { public static void main (String[] args) throws ClassNotFoundException { String classAllPath = "com.hspedu.Car" ; Class<?> cls1 = Class.forName(classAllPath); System.out.println(cls1); Class cls2 = Car.class; System.out.println(cls2); Car car = new Car (); Class cls3 = car.getClass(); System.out.println(cls3); ClassLoader classLoader = car.getClass().getClassLoader(); Class cls4 = classLoader.loadClass(classAllPath); System.out.println(cls4); System.out.println(cls1.hashCode()); System.out.println(cls2.hashCode()); System.out.println(cls3.hashCode()); System.out.println(cls4.hashCode()); Class<Integer> integerClass = int .class; Class<Character> characterClass = char .class; Class<Boolean> booleanClass = boolean .class; System.out.println(integerClass); Class<Integer> type1 = Integer.TYPE; Class<Character> type2 = Character.TYPE; System.out.println(type1); System.out.println(integerClass.hashCode()); System.out.println(type1.hashCode()); } }
哪些类型有 Class 对象 如下类型有 Class 对象
外部类,成员内部类,静态内部类,局部内部类,匿名内部类
interface : 接口
数组
enum : 枚举
annotation :注解
基本数据类型
void
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class AllTypeClass { public static void main (String[] args) { Class<String> cls1 = String.class; Class<Serializable> cls2 = Serializable.class; Class<Integer[]> cls3 = Integer[].class; Class<float [][]> cls4 = float [][].class; Class<Deprecated> cls5 = Deprecated.class; Class<Thread.State> cls6 = Thread.State.class; Class<Long> cls7 = long .class; Class<Void> cls8 = void .class; Class<Class> cls9 = Class.class; System.out.println(cls1); System.out.println(cls2); System.out.println(cls3); System.out.println(cls4); System.out.println(cls5); System.out.println(cls6); System.out.println(cls7); System.out.println(cls8); System.out.println(cls9); } }
类加载
基本说明 反射机制是 Java 实现动态语言的关键,也就是通过反射实现类动态加载
静态加载:编译时加载相关的类,如果没有则报错 ,依赖性太强
动态加载:运行时加载相关的类,如果运行时不用该类,即使不存在该类,则不报错,降低了依赖性
类加载时机
当创建对象时(new) // 静态加载
当子类被加载时,父类也加载 //静态加载
调用l类中的静态成员时 // 静态加载
通过反射 // 动态加载
类加载过程图
类加载各阶段完成任务
加载阶段 JVM 在该阶段的主要目的是将 字节码从不同的数据源(可能是 class 文件,也可能是 jar 包,甚至网络)转化为 二进制字节流加载到内存中,并生成一个代表该类的 java.lang.Cass 对象
连接阶段 - 验证
目的是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会威胁当前虚拟自身的安全
包括:文件格式验证(是否以魔数 oxcafebabe 开头),元数据验证,字节码验证和符号引用验证
可以考虑使用 - Xverify:none 参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间
连接阶段 - 准备
JVM 会在该阶段对静态变量,分配内存并默认初始化(对应数据类型的默认初始化,如 0 ,null,0L,false等),这些变量所使用的内存都将在方法区中j进行分
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class ClassLoad02 { public static void main (String[] args) { } } class A { public int n1 = 10 ; public static int n2 = 20 ; public static final int n3 = 30 ; }
连接阶段 - 解析
Initialization (初始化)
到初始化阶段,才真正开始执行类中定义的 Java 程序代码,此阶段是执行() 方法的过程
() 方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值操作和静态代码块中的语句,并进行合并
虚拟机会保证一个类的 () 方法在多线程环境中被正确的j加锁,同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的() 方法,其他线程都需要 阻塞等待,直到活动线程 执行 () 方法执行完毕
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 public class ClassLoad03 { public static void main (String[] args) throws ClassNotFoundException { B b = new B (); } } class B { static { System.out.println("B 静态代码块被执行" ); num = 300 ; } static int num = 100 ; public B () { System.out.println("B() 构造器被执行" ); } }
通过反射获取类的结构信息
java.lang.Class 类
getName : 获取全类名
getSimpleName : 获取简单类名
getFields : 获取所有 public 修饰的属性,包含本类以及父类的
getDeclaredFields : 获取本类中所有属性
getMethods : 获取所有 public 修饰的方法,包含本类以及父类的
getDeclaredMethods : 获取本类中所有方法
getConstructors : 获取本类所有 public 修饰的构造器
getDeclaredConstructors : 获取本类中所有构造器 getPackage : 以Package形式返回包信息
getSuperClass: 以Class形式返回父类信息
getInterfaces: 以Class[]形式返回接口信息
getAnnotations: 以Annotation[]形式返回注解信息
java.lang.reflect.Field 类
java.lang.reflect.Method 类
getModifiers : 以 int 形式返回修饰符
[说明:默认修饰符是0,public是1,private是2,protected是4,static是8,final是16]
getReturnType : 以 Class 形式获取返回类型
getName : 返回方法名
getParameterTypes : 以 Class[] 返回参数类型数组
java.lang.reflect.Constructor 类
getModifiers:以int形式返回修饰符
getName:返回构造器名(全类名)
getParameterTypes : 以 Class[] 返回参数类型数组
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 public class ReflectionUtils { public static void main (String[] args) { } @Test public void api_02 () throws ClassNotFoundException, NoSuchMethodException { Class<?> personCls = Class.forName("com.hspedu.reflection.Person" ); Field[] declaredFields = personCls.getDeclaredFields(); for (Field declaredField : declaredFields) { System.out.println("本类中所有属性=" + declaredField.getName() + " 该属性的修饰符值=" + declaredField.getModifiers() + " 该属性的类型=" + declaredField.getType()); } Method[] declaredMethods = personCls.getDeclaredMethods(); for (Method declaredMethod : declaredMethods) { System.out.println("本类中所有方法=" + declaredMethod.getName() + " 该方法的访问修饰符值=" + declaredMethod.getModifiers() + " 该方法返回类型" + declaredMethod.getReturnType()); Class<?>[] parameterTypes = declaredMethod.getParameterTypes(); for (Class<?> parameterType : parameterTypes) { System.out.println("该方法的形参类型=" + parameterType); } } Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors(); for (Constructor<?> declaredConstructor : declaredConstructors) { System.out.println("====================" ); System.out.println("本类中所有构造器=" + declaredConstructor.getName()); Class<?>[] parameterTypes = declaredConstructor.getParameterTypes(); for (Class<?> parameterType : parameterTypes) { System.out.println("该构造器的形参类型=" + parameterType); } } } @Test public void api_01 () throws ClassNotFoundException, NoSuchMethodException { Class<?> personCls = Class.forName("com.hspedu.reflection.Person" ); System.out.println(personCls.getName()); System.out.println(personCls.getSimpleName()); Field[] fields = personCls.getFields(); for (Field field : fields) { System.out.println("本类以及父类的属性=" + field.getName()); } Field[] declaredFields = personCls.getDeclaredFields(); for (Field declaredField : declaredFields) { System.out.println("本类中所有属性=" + declaredField.getName()); } Method[] methods = personCls.getMethods(); for (Method method : methods) { System.out.println("本类以及父类的方法=" + method.getName()); } Method[] declaredMethods = personCls.getDeclaredMethods(); for (Method declaredMethod : declaredMethods) { System.out.println("本类中所有方法=" + declaredMethod.getName()); } Constructor<?>[] constructors = personCls.getConstructors(); for (Constructor<?> constructor : constructors) { System.out.println("本类的构造器=" + constructor.getName()); } Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors(); for (Constructor<?> declaredConstructor : declaredConstructors) { System.out.println("本类中所有构造器=" + declaredConstructor.getName()); } System.out.println(personCls.getPackage()); Class<?> superclass = personCls.getSuperclass(); System.out.println("父类的class对象=" + superclass); Class<?>[] interfaces = personCls.getInterfaces(); for (Class<?> anInterface : interfaces) { System.out.println("接口信息=" + anInterface); } Annotation[] annotations = personCls.getAnnotations(); for (Annotation annotation : annotations) { System.out.println("注解信息=" + annotation); } } } class A { public String hobby; public void hi () { } public A () { } public A (String name) { } } interface IA {} interface IB {} @Deprecated class Person extends A implements IA , IB { public String name; protected static int age; String job; private double sal; public Person () { } public Person (String name) { } private Person (String name, int age) { } public void m1 (String name, int age, double sal) { } protected String m2 () { return null ; } void m3 () { } private void m4 () { } }
通过反射创建对象
方式一:调用类中的oublic修饰的无参构造器
方式二:调用类中的指定构造器
Class类相关方法
newlnstance:调用类中的无参构造器,获取对应类的对象
getConstructor(Class..clazz):根据参数列表,获取对应的publici构造器对象
getDecalaredConstructor(Class..clazz):根据参数列表,获取对应的所有构造器对象
Constructor类相关方法
setAccessible:暴破
newInstance(Object..obj):调用构造器
案例演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 public class ReflecCreateInstance { public static void main (String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { Class<?> userClass = Class.forName("com.hspedu.reflection.User" ); Object o = userClass.newInstance(); System.out.println(o); Constructor<?> constructor = userClass.getConstructor(String.class); Object hsp = constructor.newInstance("hsp" ); System.out.println("hsp=" + hsp); Constructor<?> constructor1 = userClass.getDeclaredConstructor(int .class, String.class); constructor1.setAccessible(true ); Object user2 = constructor1.newInstance(100 , "张三丰" ); System.out.println("user2=" + user2); } } class User { private int age = 10 ; private String name = "韩顺平教育" ; public User () { } public User (String name) { this .name = name; } private User (int age, String name) { this .age = age; this .name = name; } public String toString () { return "User [age=" + age + ", name=" + name + "]" ; } }
通过反射访问类中的成员
访问属性
根据属性名获取Field对象
Field f = clazz对象.getDeclaredField(属性名):
暴破:f.setAccessible(true); //f是Field
访问
f.set(o,值):/o表示对象
syso(f.get(o):/o表示对象
注意 :如果是静态属性,则set和get中的参数o,可以写成null
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class ReflecAccessProperty { public static void main (String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException { Class<?> stuClass = Class.forName("com.hspedu.reflection.Student" ); Object o = stuClass.newInstance(); System.out.println(o.getClass()); Field age = stuClass.getField("age" ); age.set(o, 88 ); System.out.println(o); System.out.println(age.get(o)); Field name = stuClass.getDeclaredField("name" ); name.setAccessible(true ); name.set(null , "老韩~" ); System.out.println(o); System.out.println(name.get(o)); System.out.println(name.get(null )); } } class Student { public int age; private static String name; public Student () { } public String toString () { return "Student [age=" + age + ", name=" + name + "]" ; } }
访问方法
根据方法名和参数列表获取Method方法对象:Method m= clazz.getDeclaredMethod(方法名,XX.class); //得到本类的所有方法
获取对象:Object o=clazz.newlnstance() ;
暴破:m.setAccessible(true);
访问:Object returnValue=m.invoke(o,实参列表); // o 就是对象
注意:如果是静态方法,则invoke的参数o,可以写成null
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 public class ReflecAccessMethod { public static void main (String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException { Class<?> bossCls = Class.forName("com.hspedu.reflection.Boss" ); Object o = bossCls.newInstance(); Method hi = bossCls.getDeclaredMethod("hi" , String.class); hi.invoke(o, "韩顺平教育~" ); Method say = bossCls.getDeclaredMethod("say" , int .class, String.class, char .class); say.setAccessible(true ); System.out.println(say.invoke(o, 100 , "张三" , '男' )); System.out.println(say.invoke(null , 200 , "李四" , '女' )); Object reVal = say.invoke(null , 300 , "王五" , '男' ); System.out.println("reVal 的运行类型=" + reVal.getClass()); Method m1 = bossCls.getDeclaredMethod("m1" ); Object reVal2 = m1.invoke(o); System.out.println("reVal2的运行类型=" + reVal2.getClass()); } } class Monster {}class Boss { public int age; private static String name; public Boss () { } public Monster m1 () { return new Monster (); } private static String say (int n, String s, char c) { return n + " " + s + " " + c; } public void hi (String s) { System.out.println("hi " + s); } }
Java 8 Java 8 新特性
速度更快
代码更少(增加了新的语法:Lambda表达式)
引入强大的 Stream APl
便于并行
最大化减少空指针异常:Optional
Nashorn
引擎,允许在JVM上运行 JS
应用
并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。相比较串行的流,并行的流可以很大程度上提高程序的执行效率。
Java 8中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API
可以声明性地通过 parallel()
与 sequential()
在并行流与顺序流之间进行切换
Lambda 表达式
Lambda 表达式概述 Lambda 是一个匿名函数,可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使 Java 的语言表达能力得到了提升
使用 Lambda 表达式 前后的差异 调用 Runnable 接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Test public void test1 () { Runnable r1 = new Runnable () { @Override public void run () { System.out.println("hello Lambda!" ); } }; r1.run(); System.out.println("========================" ); Runnable r2 = () -> System.out.println("hi Lambda!" ); r2.run(); }
使用Comparator接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Test public void test2 () { Comparator<Integer> com1 = new Comparator <Integer>() { @Override public int compare (Integer o1, Integer o2) { return Integer.compare(o1,o2); } }; int compare1 = com1.compare(12 , 32 ); System.out.println(compare1); System.out.println("===================" ); Comparator<Integer> com2 = (o1,o2) -> Integer.compare(o1,o2); int compare2 = com2.compare(54 , 21 ); System.out.println(compare2); System.out.println("===================" ); Comparator<Integer> cpm3 = Integer::compareTo; int compare3 = cpm3.compare(12 , 12 ); System.out.println(compare3); }
Lambda 表达式用法 基本语法 1.举例: (o1,o2) -> Integer.compare(o1,o2);
2.格式:
->
:lambda 操作符 或 箭头操作符
->
左边:lambda 形参列表 (其实就是接口中的抽象方法的形参列表)
->
右边:lambda 体(其实就是重写的抽象方法的方法体)
Lambda 表达式使用 格式一:无参,有返回值 1 Runnable r1 = () -> {System.out.println(“hello Lamdba!”)}
格式二:Lambda 需要一个参数但是没有返回值 1 Consumer<String> con = (String str) -> {System.out.println(str)}
格式三:数据类型可省,因为可由编译器推断出,称为类型推断 1 Consumer<String> con = (str) -> {System.out.println(str)}
格式四:Lambda 若只需要一个参数,小括号可以省略 1 Consumer<String> con = str -> {System.out.println(str)}
格式五:Lambda 需要两个以上的参数,多条执行语句,并且可以有返回值 1 2 3 4 Comparator<Integer>com = (o1,o1) -> { Syste.out.println("Lamdba表达式使用" ); return Integer.compare(o1,o2); }
格式六:当Lambda 体只有一条语句时,return 和 大括号若有都可以省略 1 Comparator<Integer>com = (o1,o1) -> Integer.compare(o1,o2);
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 public class LamdbaTest2 { @Test public void test1 () { Runnable r1 = new Runnable () { @Override public void run () { System.out.println("Hello Lamdba" ); } }; r1.run(); System.out.println("====================" ); Runnable r2 = () -> { System.out.println("Hi Lamdba" ); }; r2.run(); } @Test public void test2 () { Consumer<String> con = new Consumer <String>() { @Override public void accept (String s) { System.out.println(s); } }; con.accept("你好啊Lambda!" ); System.out.println("====================" ); Consumer<String> con1 = (String s) -> { System.out.println(s); }; con1.accept("我是Lambda" ); } @Test public void test3 () { Consumer<String> con = new Consumer <String>() { @Override public void accept (String s) { System.out.println(s); } }; con.accept("你好啊Lambda!" ); System.out.println("====================" ); Consumer<String> con1 = (s) -> { System.out.println(s); }; con1.accept("我是Lambda" ); } @Test public void test () { ArrayList<String> list = new ArrayList <>(); int [] arr = {1 ,2 ,3 ,4 }; } @Test public void test4 () { Consumer<String> con = new Consumer <String>() { @Override public void accept (String s) { System.out.println(s); } }; con.accept("你好啊Lambda!" ); System.out.println("====================" ); Consumer<String> con1 = s -> { System.out.println(s); }; con1.accept("我是Lambda" ); } @Test public void test5 () { Comparator<Integer> com1 = new Comparator <Integer>() { @Override public int compare (Integer o1, Integer o2) { System.out.println(o1); System.out.println(o2); return Integer.compare(o1, o2); } }; System.out.println(com1.compare(23 , 45 )); System.out.println("====================" ); Comparator<Integer> com2 = (o1, o2) -> { System.out.println(o1); System.out.println(o2); return o1.compareTo(o2); }; System.out.println(com2.compare(23 , 12 )); } @Test public void test6 () { Comparator<Integer> com1 = new Comparator <Integer>() { @Override public int compare (Integer o1, Integer o2) { return Integer.compare(o1, o2); } }; System.out.println(com1.compare(23 , 45 )); System.out.println("====================" ); Comparator<Integer> com2 = (o1, o2) -> o1.compareTo(o2); System.out.println(com2.compare(23 , 12 )); } @Test public void test7 () { Consumer<String> con1 = new Consumer <String>() { @Override public void accept (String s) { System.out.println(s); } }; con1.accept("hi!" ); System.out.println("====================" ); Consumer<String> con2 = s -> System.out.println(s); con2.accept("hello" ); } }
Lambda 表达式使用总结
->
左边:lambda 形参列表的参数类型可以省略(类型推断);如果 lambda 形参列表只有一个参数,其一对 ()
也可以省略
->
右边:lambda 体应该使用一对 {}
包裹;如果 lambda 体只有一条执行语句(可能是 return
语句),省略这一对 {}
和 return
关键字
Lambda 表达式总结
Lambda 表达式的本质:作为函数式接口的实例
如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口。我们可以在一个接口上使用 @FunctionalInterface
注解,这样做可以检查它是否是一个函数式接口。
因此以前用匿名实现类表示的现在都可以用 Lambda 表达式来写
函数式接口 函数式接口概述
只包含 一个抽象方法 的接口,称为函数式接口。
可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)。
可以在一个接口上使用 @FunctionalInterface
注解,这样做可以检查它是否是一个函数式接口。同时 javadoc
也会包含一条声明,说明这个接口是一个函数式接口。
Lambda 表达式的本质:作为函数式接口的实例
在 java.util.function
包下定义了Java 8的丰富的函数式接口
自定义函数接口
代码示例
1 2 3 4 @FunctionalInterface public interface MyInterface { void method1 () ; }
Java 内置函数式接口 四大核心函数式接口
函数式接口
参数类型
返回类型
用途
Consumer 消费型接口
T
void
对类型为T的对象应用操作,包含方法:void accept(T t)
Supplier 供给型接口
无
T
返回类型为 T的对象,包含方法 : T get()
Function<T,R> 函数型接口
T
R
对类型为 T 的对象应用操作,并返回结果。结果是 R 类型的对象,包含方法 R apply(T t)
Predicate 断定型接口
T
boolean
确定类型为 T 的对象是否满足某约束,并返回 boolean 值。包含方法 :boolean test(T t)
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 public class LambdaTest3 { @Test public void test1 () { Learn("java" , new Consumer <String>() { @Override public void accept (String s) { System.out.println("学习什么? " + s); } }); System.out.println("====================" ); Learn("html" , s -> System.out.println("学习什么? " + s)); } private void Learn (String s, Consumer<String> stringConsumer) { stringConsumer.accept(s); } @Test public void test2 () { Supplier<String> sp = new Supplier <String>() { @Override public String get () { return new String ("我能提供东西" ); } }; System.out.println(sp.get()); System.out.println("====================" ); Supplier<String> sp1 = () -> new String ("我能通过lambda提供东西" ); System.out.println(sp1.get()); } @Test public void test3 () { Employee employee = new Employee (1001 , "Tom" , 45 , 10000 ); Function<Employee, String> func1 =e->e.getName(); System.out.println(func1.apply(employee)); System.out.println("====================" ); Function<Employee,String>func2 = Employee::getName; System.out.println(func2.apply(employee)); } @Test public void test4 () { Function<Double, Long> func = new Function <Double, Long>() { @Override public Long apply (Double aDouble) { return Math.round(aDouble); } }; System.out.println(func.apply(10.5 )); System.out.println("====================" ); Function<Double, Long> func1 = d -> Math.round(d); System.out.println(func1.apply(12.3 )); System.out.println("====================" ); Function<Double,Long>func2 = Math::round; System.out.println(func2.apply(12.6 )); } } 作者:RealPluto 链接:https: 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
其他函数接口
函数式接口
参数类型
返回类型
用途
BiFunction<T,U,R>
T,U
R
对参数类型为 T,U参数应用操作,返回 R 类型的结果。包含方法:R appply(T t,U u)
UnaryOperator (Function 子接口)
T
T
对参数类型 为T的对象进行一元运算,并返回 T 类型的结果。包含方法 为 : T apply(T t)
BinaryOperator (BiFuction 子接口)
T,T
T
对类型为 T 的对象进行 二元运算,并返回 T 类型的结果。包含方法:T apply(T t1,T t2)
BiConsumer<T,U>
T,U
void
对类型为 T,U 参数应用操作 包含方法: void accept(T t,U u)
BiPredicate<T,U>
T,U
boolean
包含方法 : boolean test(T t, u)
IntFuctoin LongFuction DoubleFuncton
int long double
R
参数分别为 int,long,double
ToIntFuctoin ToLongFuction ToDoubleFuncton
T
int long double
分别计算int,long,double 的值
使用总结 何时使用 Lambda 表达式 当需要对一个函数式接口实例化的时候,可以使用 Lambda 表达式
何时使用给定的函数式接口 如果我们开发中,需要定义个函数式接口,首先看看在已有的 jdk 提供的函数式接口是否提供了能满足要求的函数式接口。如果有直接调用即可,不需要自己再自定义了
方法的引用
方法引用的概述 方法引用可以看做是 Lambda 表达式深层次的表达。换句话说,方法引用就是 Lambda 表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法。
使用场景 当要传递给 Lambda 体的操作,已经实现的方法了,可以使用方法引用!
使用格式 类(或对象) :: 方法名
使用情况
情况1 对象 ::
非静态方法
情况2 类 ::
静态方法
情况3 类 ::
非静态方法
使用要求
要求接口中的抽象方法的形参列表和返回值类型与方法引用的方法的形参列表和返回值类型相同!(针对于情况1和情况2)
当函数式接口方法的第一个参数是需要引用方法的调用者,并且第二个参数是需要引用方法的参数(或无参数)时:ClassName::methodName
(针对于情况3)
使用建议 如果给函数式接口提供实例,恰好满足方法引用的使用情境,就可以考虑使用方法引用给函数式接口提供实例。如果不熟悉方法引用,那么还可以使用 lambda 表达式
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 public class MethodRefTest { @Test public void test1 () { Consumer<String> con1 = str -> System.out.println(str); con1.accept("中国" ); System.out.println("====================" ); PrintStream ps = System.out; Consumer con2 = ps::println; con2.accept("China" ); } @Test public void test2 () { Employee emp = new Employee (1001 , "Bruce" , 34 , 600 ); Supplier<String> sup1 = () -> emp.getName(); System.out.println(sup1.get()); System.out.println("====================" ); Supplier sup2 = emp::getName; System.out.println(sup2.get()); } @Test public void test3 () { Comparator<Integer> com1 = (t1, t2) -> Integer.compare(t1, t2); System.out.println(com1.compare(32 , 45 )); System.out.println("====================" ); Comparator<Integer> com2 = Integer::compareTo; System.out.println(com2.compare(43 , 34 )); } @Test public void test4 () { Function<Double, Long> func = new Function <Double, Long>() { @Override public Long apply (Double aDouble) { return Math.round(aDouble); } }; System.out.println(func.apply(10.5 )); System.out.println("====================" ); Function<Double, Long> func1 = d -> Math.round(d); System.out.println(func1.apply(12.3 )); System.out.println("====================" ); Function<Double, Long> func2 = Math::round; System.out.println(func2.apply(12.6 )); } @Test public void test5 () { Comparator<String> com1 = (s1, s2) -> s1.compareTo(s2); System.out.println(com1.compare("abd" , "aba" )); System.out.println("====================" ); Comparator<String> com2 = String::compareTo; System.out.println(com2.compare("abd" , "abc" )); } @Test public void test6 () { BiPredicate<String, String> pre1 = (s1, s2) -> s1.equals(s2); System.out.println(pre1.test("abc" , "abc" )); System.out.println("====================" ); BiPredicate<String, String> pre2 = String::equals; System.out.println(pre2.test("abc" , "abd" )); } @Test public void test7 () { Employee employee = new Employee (1001 , "Tom" , 45 , 10000 ); Function<Employee, String> func1 =e->e.getName(); System.out.println(func1.apply(employee)); System.out.println("====================" ); Function<Employee,String>func2 = Employee::getName; System.out.println(func2.apply(employee)); } } 作者:RealPluto 链接:https: 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
构造器和数组的引用
使用格式 方法引用:类名 ::new
数组引用:数组类型 [] :: new
使用要求 构造器引用 和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致。抽象方法的返回值类型即为构造器所属的类的类型
数组引用 可以把数组看做是一个特殊的类,则写法与构造器引用一致。
构造器引用代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 @Test public void test1 () { Supplier<Employee> sup = new Supplier <Employee>() { @Override public Employee get () { return new Employee (); } }; System.out.println(sup.get()); System.out.println("====================" ); Supplier<Employee> sup1 = () -> new Employee (1001 , "Tom" , 43 , 13333 ); System.out.println(sup1.get()); Supplier<Employee> sup2 = Employee::new ; System.out.println(sup2.get()); } @Test public void test2 () { Function<Integer, Employee> func1 = id -> new Employee (id); Employee employee = func1.apply(1001 ); System.out.println(employee); System.out.println("====================" ); Function<Integer, Employee> func2 = Employee::new ; Employee employee1 = func2.apply(1002 ); System.out.println(employee1); } @Test public void test3 () { BiFunction<Integer, String, Employee> func1 = (id, name) -> new Employee (id, name); System.out.println(func1.apply(1001 , "Tom" )); System.out.println("====================" ); BiFunction<Integer, String, Employee> func2 = Employee::new ; System.out.println(func2.apply(1002 , "Jarry" )); }
数组引用代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void test4 () { Function<Integer, String[]> func1 = length -> new String [length]; String[] arr1 = func1.apply(5 ); System.out.println(Arrays.toString(arr1)); System.out.println("====================" ); Function<Integer,String[]>func2=String[]::new ; String[] arr2 = func2.apply(10 ); System.out.println(Arrays.toString(arr2)); }
Stream API
Stream API 概述
Stream
关注的是对数据的运算,与 CPU
打交道;集合关注的是数据的存储,与内存打交道;
Java 8 提供了一套 api
,使用这套 api
可以对内存中的数据进行过滤、排序、映射、归约等操作。类似于 sql
对数据库中表的相关操作。
Stream
是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据, Stream讲的是计算!”
使用注意点
Stream
自己不会存储元素。
Stream
不会改变源对象。相反,他们会返回一个持有结果的新 Stream
。
Stream
操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
Stream 使用流程
Stream 的实例化
一系列的中间操作(过滤、映射、…)
终止操作
使用注意点
一个中间操作链,对数据源的数据进行处理
一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用
使用方法 步骤一 创建Stream 创建方式一:通过集合 Java 8的 Collection
接口被扩展,提供了两个获取流的方法:
default Stream\<E> stream()
: 返回一个顺序流
default Stream\<E> parallelStream()
: 返回一个并行流
创建方式二:通过数组 Java 8中的 Arrays
的静态方法 stream()
可以获取数组流
调用 Arrays
类的 static\<T> Stream\<T> stream(T[] array)
: 返回一个流
重载形式,能够处理对应基本类型的数组:
public static IntStream stream(int[] array)
public static LongStream stream(long[] array)
public static DoubleStream stream(double[] array)
创建方式三:通过 Stream 的 of 方法 可以调用Stream类静态方法of(),通过显示值创建一个流。可以用于接收任意数量的参数
public static \<T>Stream\<T> of(T...values)
:返回一个流
创建方式四:创建无限流 迭代: public static\<T> Stream\<T> iterate(final T seed, final UnaryOperator\<T> f)
生成: public static\<T> Stream\<T> generate(Supplier\<T> s)
代码示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 public class StreamAPITest1 { @Test public void test1 () { List<Employee> employees = EmployeeData.getEmployees(); Stream<Employee> stream = employees.stream(); Stream<Employee> employeeStream = employees.parallelStream(); } @Test public void test2 () { int [] arrs = {1 , 2 , 3 , 6 , 2 }; IntStream stream = Arrays.stream(arrs); Employee e1 = new Employee (1001 , "Tom" ); Employee e2 = new Employee (1002 , "Jerry" ); Employee[] employees = {e1, e2}; Stream<Employee> stream1 = Arrays.stream(employees); } @Test public void test3 () { Stream<Integer> integerStream = Stream.of(12 , 34 , 45 , 65 , 76 ); } @Test public void test4 () { Stream.iterate(0 , t -> t + 2 ).limit(10 ).forEach(System.out::println); Stream.generate(Math::random).limit(10 ).forEach(System.out::println); } }
步骤二:中间操作 多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为惰性求值 。
筛选与切片
方法
描述
filter(predicate p)
接受 Lambda,从流中排除某些元素
distinct()
筛选,通过流所生成元素的 hashcode() 和 equals() 去除重复元素
limit(long maxSize)
截断流,使其元素不超过给定数量
skip(long n)
跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素b不足 n 个,则返回一个空流,与limit() 互补
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Test public void test1 () { List<Employee> employees = EmployeeData.getEmployees(); Stream<Employee> employeeStream = employees.stream(); employeeStream.filter(e -> e.getSalary() > 7000 ).forEach(System.out::println); employeeStream.limit(3 ).forEach(System.out::println); System.out.println(); employeeStream.skip(3 ).forEach(System.out::println); employees.add(new Employee (1010 ,"刘" ,56 ,8000 )); employees.add(new Employee (1010 ,"刘" ,56 ,8000 )); employees.add(new Employee (1010 ,"刘" ,56 ,8000 )); employees.add(new Employee (1010 ,"刘" ,56 ,8000 )); employeeStream.distinct().forEach(System.out::println); }
映射
方法
描述
map(Functon f)
接受一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
mapToDoubleQ(ToDoubleFunction f)
接受一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的DoubleStream
mapToInt(ToIntFunction f)
接受一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的 IntStream
mapToLong(ToLongFunction f)
接受一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的 LongStream
flatMap(Function f)
接受一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 @Test public void test2 () { List<String> list = Arrays.asList("aa" , "bb" , "cc" , "dd" ); list.stream().map(str -> str.toUpperCase()).forEach(System.out::println); List<Employee> employees = EmployeeData.getEmployees(); Stream<String> nameStream = employees.stream().map(Employee::getName); nameStream.filter(name -> name.length() >3 ).forEach(System.out::println); System.out.println(); Stream<Stream<Character>> streamStream = list.stream().map(StreamAPITest2::fromStringToStream); streamStream.forEach(s ->{ s.forEach(System.out::println); }); System.out.println(); Stream<Character> characterStream = list.stream().flatMap(StreamAPITest2::fromStringToStream); characterStream.forEach(System.out::println); } public static Stream<Character>fromStringToStream(String str){ ArrayList<Character> list = new ArrayList <>(); for (Character c : str.toCharArray()) { list.add(c); } return list.stream(); } @Test public void test () { ArrayList<Object> list1 = new ArrayList <>(); list1.add(1 ); list1.add(2 ); list1.add(3 ); list1.add(4 ); ArrayList<Object> list2 = new ArrayList <>(); list2.add(5 ); list2.add(6 ); list2.add(7 ); list2.add(8 ); list1.add(list2); System.out.println(list1); list1.addAll(list2); System.out.println(list1); }
排序
方法
描述
sorted()
产生一个新流,其中按自然顺序排序
sorted(Comparator com)
产生一个新流,其中按比较器顺序排序
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Test public void test3 () { List<Integer> list = Arrays.asList(12 , 34 , 54 , 65 , 32 ); list.stream().sorted().forEach(System.out::println); List<Employee> employees = EmployeeData.getEmployees(); employees.stream().sorted().forEach(System.out::println); List<Employee> employees1 = EmployeeData.getEmployees(); employees1.stream().sorted((e1,e2)->{ int ageValue = Integer.compare(e1.getAge(), e2.getAge()); if (ageValue != 0 ){ return ageValue; }else { return -Double.compare(e1.getSalary(),e2.getSalary()); } }).forEach(System.out::println); }
步骤三:终止操作
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List
、 Integer
,甚至是 void
流进行了终止操作后,不能再次使用。
匹配和查找
方法
描述
allMatch(Predicate p)
检查是否匹配所有元素
anyMatch(Predicate p)
检查是否至少匹配一个元素
noneMatch(Predicate p)
检查是否没有匹配所有元素
findFirst()
返回第一个元素
findAny()
返回当前流中的任意元素
count()
返回流中元素总数
max(Comparator c)
返回流中 的最大值
min(Comparator c)
返回流中的最小值
forEach(Consumer cons)
内部迭代(使用 Collection 接口需要用户去做迭代,成为外部迭代,相反,Stream API 使用内部迭代,帮你把迭代做了)
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 @Test public void test1 () { List<Employee> employees = EmployeeData.getEmployees(); boolean allMatch = employees.stream().allMatch(e -> e.getAge() > 18 ); System.out.println(allMatch); boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() > 5000 ); System.out.println(anyMatch); boolean noneMatch = employees.stream().noneMatch(e -> e.getName().startsWith("雷" )); System.out.println(noneMatch); Optional<Employee> first = employees.stream().findFirst(); System.out.println(first); Optional<Employee> employee = employees.parallelStream().findAny(); System.out.println(employee); } @Test public void test2 () { List<Employee> employees = EmployeeData.getEmployees(); long count = employees.stream().filter(e -> e.getSalary()>5000 ).count(); System.out.println(count); Stream<Double> salaryStream = employees.stream().map(e -> e.getSalary()); Optional<Double> maxSalary = salaryStream.max(Double::compareTo); System.out.println(maxSalary); Optional<Double> minSalary = employees.stream().map(e -> e.getSalary()).min(Double::compareTo); System.out.println(minSalary); employees.stream().forEach(System.out::println); System.out.println(); employees.forEach(System.out::println); }
归约
方法
描述
reduce(T iden,BinaryOperator b)
可以将流中元素反复结合起来,得到一个值,就返回 T
reduce(BinaryOperator b)
可以将流中的元素反复结合起来,得到一个值,就返回 Optional
备注:map
和 reduce
的连接通常称为 map-reduce
模式,因 Google 用它来进行网络搜索而出名
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Test public void test3 () { List<Integer> list = Arrays.asList(1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ); Integer sum = list.stream().reduce(0 , Integer::sum); System.out.println(sum); List<Employee> employees = EmployeeData.getEmployees(); Optional<Double> sumSalary = employees.stream().map(e -> e.getSalary()).reduce(Double::sum); System.out.println(sumSalary); }
收集
方法
描述
collect(Collector c)
将流转换成其他形式。接收一个 Collector 接口的实现,用于给 Stream中元素做汇总的方法
Collector
接口中方法的实现决定了如何对流执行收集的操作(如收集到 List
、Set
、Map
)
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public void test4 () { List<Employee> employees = EmployeeData.getEmployees(); List<Employee> employeeList = employees.stream().filter(e -> e.getSalary() > 6000 ).collect(Collectors.toList()); employeeList.forEach(System.out::println); System.out.println(); Set<Employee> employeeSet = employees.stream().filter(e -> e.getSalary() > 6000 ).collect(Collectors.toSet()); employeeSet.forEach(System.out::println); }
Optional 类的使用
Optional 类的概述
为了解决 java 中的空指针问题而生!
Optional<T> 类(java.util.Optional)
是一个容器类,它可以保存类型 T
的值,代表这个值存在。或者仅仅保存 null
,表示这个值不存在。原来用 null
表示一个值不存在,现在 Optional
可以更好的表达这个概念。并且可以避免空指针异常。
Optional 类提供的方法 Optional
类提供了很多方法,可以不用再现实的进行空值检验。
创建 Optional 类对象的方法
Optional.of(T t)
: 创建一个 Optional
实例,t
必须非空;
Optional.empty()
: 创建一个空的 Optional
实例
Optional.ofNullable(T t)
:t
可以为 null
判断 Optional 容器是否包含对象
boolean isPresent()
:判断是否包含对象
void ifPresent(Consumer<? super T> consumer)
:如果有值,就执行 Consumer
接口的实现代码,并且该值会作为参数传给它。
获取 Optional 容器的对象
T get()
:如果调用对象包含值,返回该值,否则抛异常
T orElse(T other)
:如果有值则将其返回,否则返回指定的 other
对象
T orElseGet(Supplier<? extends t> other)
:如果有值则将其返回,否则返回由 Supplier
接口实现提供的对象。
T orElseThrow(Supplier<? extends X> exceptionSupplier)
:如果有值则将其返回,否则抛出由 Supplier
接口实现提供的异常。
搭配使用
of()
和 get()
方法搭配使用,明确对象非空
ofNullable()
和 orElse()
搭配使用,不确定对象非空
应用举例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class OptionalTest { @Test public void test1 () { Optional<Object> op1 = Optional.empty(); if (!op1.isPresent()){ System.out.println("数据为空" ); } System.out.println(op1); System.out.println(op1.isPresent()); System.out.println(op1.get()); } @Test public void test2 () { String str = "hello" ; Optional<String> op1 = Optional.of(str); String str1 = op1.get(); System.out.println(str1); } @Test public void test3 () { String str = "Beijing" ; str = null ; Optional<String> op1 = Optional.ofNullable(str); System.out.println(op1); String str2 = op1.orElse("shanghai" ); System.out.println(str2); } }
使用 Optional
类避免产生空指针异常 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 public class GirlBoyOptionalTest { public String getGrilName1 (Boy boy) { if (boy != null ){ Girl girl = boy.getGirl(); if (girl != null ){ return girl.getName(); } } return null ; } public String getGirlName2 (Boy boy) { Optional<Boy> boyOptional = Optional.ofNullable(boy); Boy boy1 = boyOptional.orElse(new Boy (new Girl ("迪丽热巴" ))); Girl girl = boy1.getGirl(); Optional<Girl> girlOptional = Optional.ofNullable(girl); Girl girl1 = girlOptional.orElse(new Girl ("古力娜扎" )); return girl1.getName(); } @Test public void test1 () { Boy boy = null ; System.out.println(getGrilName1(boy)); boy = new Boy (); System.out.println(getGrilName1(boy)); boy = new Boy (new Girl ("杨幂" )); System.out.println(getGrilName1(boy)); } @Test public void test2 () { Boy boy = null ; System.out.println(getGirlName2(boy)); boy = new Boy (); System.out.println(getGirlName2(boy)); boy = new Boy (new Girl ("杨幂" )); System.out.println(getGirlName2(boy)); } }
简答题 1. == 与 equals 的区别
== :
概念: 比较运算符 用于基本数据类型: 可以,判断值是否相等 用于引用类型: 可以,判断两个对象是否相等
equals:
概念: Object类的方法 用于基本数据类型: 不可以 用于引用类型: 可以,默认两个对象是否为一个对象,但是子类往往重写该方法,比较子类属性是否相等
2. 什么是多态
多态: 方法或者对象具有多种形态,是 OOP 的第三大特征,是建立在封装和继承基础之上
多态具体提现
方法多态
1.重载体现多态 2.重写体现多态
对象多态
1.对象的编译类型和运行类型可以不一致,编译类型在定义时就确定,不能变化
**2.对象的运行类型是可以变化的,可以通过 getClass() 来查看运行类型 **
3.编译类型看定义时 = 号 左边,运行类型看 = 号 右边 testing
3.Java 的动态绑定机制 HashSet 和 TreeSet 如何去重