Java进阶简介 主要的知识点来自于黑马程序员的视频:BV1TE41177mP
day1-复习回顾、静态、继承、引用类型使用 定义类
封装
面向对象的三大特征 之一:封装,继承,多态 。
形成了规范,即使毫无意义还是会这样写代码!
合理隐藏,合理暴露。
封装的规范:成员变量私有,方法一般公开,提供成套的getter
和setter
方法暴露成员变量的取值和赋值,public
修饰符
封装的作用:提高安全性,提高代码的组件化思想。
封装已经成为Java
代码的规范,即使毫无意义,我们也要这样写代码(成员变量私有,方法公开)
this关键字
this
代表了当前对象的引用。
this
可以出现在构造器和方法中。
this
出现在构造器中代表构造器正在初始化的对象。
this
出现在方法中,哪个对象调用方法,this
就代表哪个对象。
this
可以访问对象的成员变量,区分成员变量是局部的还是对象中的成员变量。
static关键字 Java
是通过成员变量是否有static
修饰来区分是类的还是属于对象的
没有static
修饰的方法和变量是属于每个对象的
有static
修饰的方法和成员变量属于类的
按照有无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 public class Student { public static String schoolName = "黑马" ; private String name; private int age ; public static void main (String[] args) { System.out.println(Student.schoolName); System.out.println(schoolName); Student swk = new Student (); swk.name = "孙悟空" ; System.out.println(swk.name); System.out.println(swk.age); System.out.println(swk.schoolName); } }
成员方法 :
静态方法 :有static
修饰的成员方法称为静态方法也叫类方法,属于类本身的,直接用类名访问 即可。
实例方法 :无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 public class Student { private String name; private int age ; public static void inAddr () { System.out.println("我们都在天河区吉山村happy的学习Java!" ); } public void eat () { System.out.println(name + "已经" +age+"岁,在吃好吃的!!" ); } public static void main (String[] args) { Student.inAddr(); inAddr(); Student zbj = new Student (); zbj.name = "猪刚鬣" ; zbj.age = 1000 ; zbj.eat(); zbj.inAddr(); } }
关于static
常考的八类题:
实例方法是否可以直接访问实例成员变量?可以的,因为它们都属于对象。
实例方法是否可以直接访问静态成员变量?可以的,静态成员变量可以被共享访问。
实例方法是否可以直接访问实例方法? 可以的,实例方法和实例方法都属于对象。
实例方法是否可以直接访问静态方法?可以的,静态方法可以被共享访问!
静态方法是否可以直接访问实例变量? 不可以的,实例变量必须用对象访问!!
静态方法是否可以直接访问静态变量? 可以的,静态成员变量可以被共享访问。
静态方法是否可以直接访问实例方法? 不可以的,实例方法必须用对象访问!!
静态方法是否可以直接访问静态方法?可以的,静态方法可以被共享访问!!
也就是说实例方法啥都可以访问,静态方法只能访问静态方法或者静态变量
继承 继承的概述 面向对象的三大特征:封装、继承和多态
继承是Java中一般到特殊的关系,是一种子类到父类的关系。例如:学生类继承了人类。 猫类继承了动物类。
被继承的类称为:父类/超类。继承父类的类称为:子类
继承可以提高代码的复用性
子类直接继承父类,就可以直接使用父类的这些代码了(相同代码重复利用)
子类继承了一个父类,子类就可以直接得到父类的属性(成员变量)和行为(方法)了。
继承的例子 1 2 3 4 5 6 7 class Animal { }class Cat extends Animal { }
继承的优势可以把相同的代码定义在父类中,子类可以直接继承使用。
这样就可以提高代码的复用性 :相同代码只需要在父类中写一次就可以了。
子类不能继承父类的内容
子类继承父类,子类就得到了父类的属性和行为。
但是并非所有父类的属性和行为等子类都可以继承。
子类不能继承父类的东西 :子类不能继承父类的构造器,子类有自己的构造器。(没有争议的)
有争议的观点(拓展):
子类是否可以继承父类的私有成员 (私有成员变量,私有成员方法)?
子类是可以继承父类的私有成员的,只是不能直接访问而已 。
以后可以暴力去访问继承自父类的私有成员~~~
子类是否可以继承父类的静态成员?
子类是不能继承父类的静态成员的
子类只是可以访问父类的静态成员 ,父类的静态成员只有一份可以被子类共享访问。
共享并非继承
成员变量的访问特点 就近原则 :子类有找子类,子类没有找父类,父类没有就报错
1 2 3 4 5 6 7 8 9 10 11 12 13 class Animal { public String name = "动物名称" ; }class Cat extends Animal { public String name = "子类名称" ; public void show () { String name = "局部名称" ; System.out.println(name); System.out.println(this .name); System.out.println(super .name); } }
this
代表了当前对象的引用,可以用于访问当前子类对象的成员变量。
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 public class TestDemo { public static void main (String[] args) { Cat cat = new Cat (); cat.run(); cat.eat(); } }class Animal { public void run () { System.out.println("动物可以跑~~~~" ); } public void eat () { System.out.println("吃东西~~~~" ); } }class Cat extends Animal { public void run () { System.out.println("🐱跑的贼溜~~~~" ); } }
方法重写 子类继承了父类,子类就得到了父类的某个方法。但是子类觉得父类的这个方法不好用或者无法满足自己的需求,子类重写一个与父类申明一样的方法来覆盖父类的该方法,子类的这个方法就进行了方法重写。
方法重写的校验注解: @Override
Java
建议在重写的方法上面加上一个@Override
注解。
方法一旦加了这个注解,那就必须是成功重写父类 的方法,否则报错!
Override
优势:可读性好,安全,优雅
方法重写的具体要求:
子类重写方法的名称和形参列表必须与父类被重写方法一样 。
子类重写方法的返回值类型申明要么与父类一样,要么比父类方法返回值类型范围更小 。(以后再了解)
子类重写方法的修饰符权限应该与父类被重写方法的修饰符权限相同或者更大 。(以后再了解)
子类重写方法申明抛出的异常应该与父类被重写方法申明抛出的异常一样或者范围更小 !(以后再了解)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Wolf extends Animal { @Override public void run () { System.out.println("🐺跑的贼快~~~" ); } }class Animal { public void run () { System.out.println("动物可以跑步~~~" ); } }
方法重写是子类重写一个与父类申明一样的方法覆盖父类的方法。
方法重写建议加上@Override
注解。
方法重写的核心要求:方法名称形参列表必须与被重写方法一致!!
建议申明不变,重新实现 。
调用父类被重写的方法使用super
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class SportMan extends People { @Override public void run () { System.out.println("运动员跑的贼快~~~~~" ); } public void go () { super .run(); run(); } }class People { public void run () { System.out.println("人会跑~" ); } }
super
可以用在子类的实例方法中调用父类被重写的方法
静态方法和私有方法不可以 被重写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Mac extends Computer { public void go () { } public static void test () { } }class Computer { public static void test () { System.out.println("super test" ); } private void go () { } }
继承后构造器的特点 子类的全部构造器默认一定会先访问父类的无参数构造器,再执行子类自己的构造器 ,主要的原因是
子类的构造器的第一行默认有一个super()
调用父类的无参数构造器,写不写都存在
子类继承父类,子类就得到了父类的属性和行为
当我们调用子类构造器初始化子类对象数据的时候,必须先调用父类构造器初始化继承自父类的属性和行为
super调用父类构造器 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 44 45 46 47 48 49 class Monkey extends Animal { public Monkey (String name, int age, char sex) { super (name , age , sex) ; } public void eatBanana () { System.out.println(getName()+"-->" +getAge()+"-->" +getSex()+"在吃🍌~~~" ); } }class Animal { private String name; private int age; private char sex; public Animal () { } public Animal (String name, int age, char sex) { this .name = name; this .age = age; this .sex = sex; } 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; } public char getSex () { return sex; } public void setSex (char sex) { this .sex = sex; } }
super
调用父类构造器的内存分布图
this和super关键字使用总结 this
代表了当前对象的引用(继承中指代子类对象):
this
子类成员变量
this
子类成员方法
this(...)
可以根据参数匹配访问本类其他构造器
super
代表了父类对象的引用(继承中指代了父类对象空间)
super
父类成员变量
super
父类的成员方法
super(...)
可以根据参数匹配访问父类的构造器
this(...)
和super(...)
必须放在构造器的第一行 ,否则报错
所以this(...)
和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 class Student { private String name ; private int age ; private String schoolName ; public Student () { } public Student (String name , int age) { this (name , age , "黑马" ); } public Student (String name, int age, String schoolName) { this .name = name; this .age = age; this .schoolName = schoolName; } 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; } public String getSchoolName () { return schoolName; } public void setSchoolName (String schoolName) { this .schoolName = schoolName; } }
继承的特点
引用类型作为方法参数和返回值
除了基本数据类型都是引用数据类型
引用类型可以作为方法的参数类型和返回值类型
引用数据类型可以在一切可以使用类型的地方使用
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 TestDemo { public static void main (String[] args) { Dog jinMao = new Dog (); go(jinMao); System.out.println("--------------" ); Dog dog = createDog(); dog.run(); } public static Dog createDog () { return new Dog (); } public static void go (Dog a) { System.out.println("比赛开始。。。" ); a.run(); System.out.println("比赛结束。。。" ); } }class Dog { public void run () { System.out.println("🐕跑的贼溜~~~" ); } }
引用类型作为成员变量的类型 Address.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Address { private String code; private String name; private double x; private double y; public Address () { } public Address (String code, String name, double x, double y) { this .code = code; this .name = name; this .x = x; this .y = y; } }
Student.java
1 2 3 4 5 6 7 public class Student { private String name; private int age ; private Address address; }
day2-抽象类、接口、代码块、final、单例、枚举 抽象类 抽象类的概述 父类指导之类一定要完成某个功能,但是每个之类完成的情况是不一样的。子类以后也只会用自己重写的功能,那么父类的该功能就可以定义成抽象方法,子类重写调用自己的方法。所以父类的该功能就可以定义为抽象的方法。拥有冲向方法的类必须定义为抽象类。
抽象方法 :没有方法体,只有方法签名,必须用abstract
修饰的方法就是抽象方法。
抽象类 :拥有抽象方法的类必须定义成抽象类,必须用abstract
修饰。
1 2 3 4 5 6 7 8 9 10 11 12 class Wolf extends Animal { @Override public void run () { System.out.println("🐺跑的贼贼溜~~~" ); } }abstract class Animal { public abstract void 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 class Manager extends Employee { @Override public void work () { System.out.println("班主任需要管理班级~~~~" ); } @Override public void run () { } }class Techer extends Employee { @Override public void work () { System.out.println("老师需要授课~~~~" ); } @Override public void run () { } }abstract class Employee { public abstract void work () ; public abstract void 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 class Teacher extends Template { @Override public String writeMain () { return "\t\t我爸就是好,有多好,做他儿子才能懂~~~" ; } }class Student extends Template { @Override public String writeMain () { return "\t\t我爸爸很牛,我爸爸是马云,就是爽,很有钱~~~~" ; } }abstract class Template { private String title = "\t\t\t\t\t\t《我的爸爸》" ; private String one = "\t\t我的爸爸很牛逼,到底有多牛呢,请看如下说明:" ; private String last = "\t\t以上就是我的爸爸,简直太好了,下辈子还要做他儿子!" ; public void write () { System.out.println(title); System.out.println(one); System.out.println(writeMain()); System.out.println(last); } public abstract String writeMain () ; }
抽象类的注意事项
抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。 理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
抽象类一定有而且是必须有构造器,是供子类创建对象时,初始化父类成员使用的。 理解:子类的构造器中,有默认的super(),需要访问父类构造器。
抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
抽象类的子类,必须重写抽象父类中所有的抽象方法,否则子类也必须定义成抽象类。
抽象类存在的意义是为了被子类继承,抽象类体现的是模板思想。 理解:抽象类中已经实现的是模板中确定的成员, 抽象类不确定如何实现的定义成抽象方法,交给具体的子类去实现。
接口 接口的概述 接口体现的是规范思想,实现接口的子类必须重写完接口的全部抽象方法
接口是更加彻底的抽象,在JDK 1.8之前接口中只能是抽象方法和常量
定义格式
成分研究(JDK 1.8之前)
接口中抽象方法默认加上public abstract
修饰,可以省略不写
常量是指有public static final
修饰的成员变量,有且仅能被复制一次 ,值不能改变
常量名称规范要求全部大写,多个单词下划线连接
常量修饰public static final
可以省略不写,默认会加上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public interface InterfaceDemo { String SCHOOL_NAME = "黑马" ; void run () ; void work () ; }
接口的基本实现 子类和父类是继承,实现类和接口是实现关系。接口是用来被类实现的,实现接口的类是实现类
子类–>继承–>父类
实现类–>实现–>接口
类实现接口的格式:
1 2 3 修饰符 class 实现类名称 implements 接口1 ,接口2 ,接口3 ,....{ }
例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class PingPongMan implements SportMan { private String name; public PingPongMan (String name) { this .name = name; } @Override public void run () { System.out.println(name+"必须天天运动。正在🏃训练~~~" ); } @Override public void win () { System.out.println(name+"参加比赛中~~~" ); } }interface SportMan { void run () ; void win () ; }
接口的使命就是要求实现接口的类必须有run()
和win()
方法
接口可以多实现
一个类实现接口必须重写完接口中全部抽象方法,否则这个类必须定义成抽象类
接口的多实现
一个类如果实现了多个接口,必须重写完全部接口中的全部抽象方法,否则这个类必须定义为抽象类
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 class PingPongMan implements SportMan , Law{ @Override public void rule () { } @Override public void run () { } @Override public void win () { } }interface Law { void rule () ; void run () ; }interface SportMan { void run () ; void win () ; }
接口与接口的多继承
类与类是单继承关系:一个类只能继承一个直接父类
类与接口是多继承关系:一个类可以同时实现多个接口
接口与接口是多继承关系:一个接口可以同时继承多个接口
接口与接口的多继承,用一个接口合并多个接口
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 class PingPongMan implements SportMan { @Override public void eat () { } @Override public void rule () { } @Override public void run () { } @Override public void goAbroad () { } }interface Food { void eat () ; }interface Law { void rule () ; }interface SportMan extends Law , Food { void run () ; void goAbroad () ; }
JDK1.8之后接口新增的方法 JDK1.8开始之后接口新增的三个方法,了解即可
默认方法 就是之前写的实例方法
必须用default
修饰
默认会加public
修饰
只能用接口的实现类的对象来调用
静态方法
可以直接加static
修饰
默认会加public
修饰
接口的静态方法只能用接口的类名称调用
私有方法 从JDK 1.9开始才支持的
其实就是私有的实例方法,必须加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 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class InterfaceDemo { public static void main (String[] args) { PingPongMan zjk = new PingPongMan (); zjk.run(); zjk.work(); InterfaceJDK8.inAddr(); } }class PingPongMan implements InterfaceJDK8 { @Override public void work () { System.out.println("工作中。。。" ); } }interface InterfaceJDK8 { void work () ; default void run () { go(); System.out.println("开始跑步🏃~~~~" ); } static void inAddr () { System.out.println("我们在吉山区~~~~" ); } private void go () { System.out.println("开始。。" ); } }
接口的注意事项
如果实现了多个接口,多个接口存在同名的静态方法 并不会从冲突,原因是只能通过各自接口方法访问各自静态方法
当一个类,即继承一个父类,又实现若干个接口时,父类的成员方法与接口中的默认方法重名时,之类就近 选择执行父类的成员方法
当一个类实现多个接口时,多个接口存在同名的默认方法,实现类必须重写这个方法
代码块 代码块按照有无static
修饰可以分为:静态代码块、实例代码块
静态代码块 静态代码块:必须用static
修饰,必须放在类下,与类一起优先加载执行
静态代码块可以用于执行类的方法之前进行静态资源的初始化操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class CodeDemo01 { public static String schoolName ; public static ArrayList<String> lists = new ArrayList <>(); static { System.out.println("静态代码块被触发执行~~~~~~~" ); schoolName = "黑马" ; lists.add("3" ); lists.add("4" ); lists.add("5" ); } public static void main (String[] args) { System.out.println(schoolName); System.out.println(lists); } }
实例代码块
实例代码块直接用{}
括起来,无需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 public class CodeDemo02 { private String name; private ArrayList<String> lists = new ArrayList <>(); { name = "小手" ; lists.add("东" ); lists.add("南" ); lists.add("西" ); lists.add("北" ); System.out.println("实例代码块被触发执行一次~~~~~~~~" ); } public CodeDemo02 () { } public CodeDemo02 (String name) { } public static void main (String[] args) { CodeDemo02 c = new CodeDemo02 (); System.out.println(c.name); System.out.println(c.lists); new CodeDemo02 (); new CodeDemo02 (); } }
final关键词 final
可以用于修饰类、方法、变量
final
修饰类:类不能被继承了
final
修饰方法:方法不能被重写
final
修饰变量:变量有且仅能被赋值一次
局部变量-只能赋值一次,不能在更改
实例成员变量
显示初始化,在定义成员变量的时候立马赋值
实例代码块中赋值一次
构造器初始化,在构造器中赋值一次
final
和abstract
的关系
互斥关系,不能同时修饰类或者同时修饰方法
常量 :有public static final
修饰,名称字母全部大写,多个单词用下划线连接
单例设计模式 单例 的意思是一个类永远只存在一个对象,不能创建多个对象
开发中有很多类的对象我们只需要一个对象,例如虚拟机,任务管理器对象
对象越多越占内存,有时候只需要一个对象就可以实现业务,单例可以节约内存,提高性能
饿汉单例设计模式 通过类获取单例对象的时候,对象已经提前准备做好了
设计步骤:
定义一个类,把构造器私有
定义一个静态变量存储一个对象
提供一个返回单例对象的方法
1 2 3 4 5 6 7 8 9 10 11 12 class Singleton01 { private static final Singleton01 INSTANCE = new Singleton01 (); private Singleton01 () { } public static Singleton01 getInstance () { return INSTANCE; } }
懒汉单例设计模式 通过类获取单例对象的时候发现没有对象才会去创建一个对象
设计步骤:
定义一个类,把构造器私有
定义一个静态成员变量用于存储一个对象
提供一个返回单例对象的方法,判断对象不存在才创建一次,存在直接返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Singleton02 { public static Singleton02 instance ; private Singleton02 () { } public static Singleton02 getInstance () { if (instance == null ){ instance = new Singleton02 (); } return instance; } }
枚举 枚举类的作用:是为了做信息的标志和信息分类
枚举类基本语法
例如
1 2 3 4 5 6 7 8 enum Sex { BOY , GIRL; }enum Season { SPRING , SUMMER , AUTUMN , WINTER; }
枚举类反编译以后的源代码
1 2 3 4 5 6 7 8 9 public final class Season extends java .lang.Enum<Season> { public static final Season SPRING = new Season (); public static final Season SUMMER = new Season (); public static final Season AUTUMN = new Season (); public static final Season WINTER = new Season (); public static Season[] values(); public static Season valueOf (java.lang.String) ; }
枚举类的特点
枚举类是final
修饰的,不能被继承
枚举类默认继承了枚举类型java.lang.Enum
枚举类的第一行罗列的是枚举类的对象,而且是用常量存储的
所以枚举类的第一行写的都是常量名称,默认存储了枚举对象
枚举类的构造器默认是私有的
枚举类相当于是多例设计模式
Java
建议做信息标志和信息分类应该使用枚举 实现,比较优雅,可以实现可读性,而且入参受限制,不能乱输入
day3-多态、包、权限修饰符、内部类、object类、Date类 多态==重点== 面向对象的三大特征:封装、继承、多态
多态的形式:
1 2 父类类型 变量名 = 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 public class PolymorphicDemo { public static void main (String[] args) { Animal dlam = new Cat (); dlam.run(); System.out.println(dlam.name); Animal taiDi = new Dog (); taiDi.run(); System.out.println(taiDi.name); } }class Dog extends Animal { public String name = "🐶名称Dog" ; @Override public void run () { System.out.println("🐕跑的贼快~~~~!" ); } }class Cat extends Animal { public String name = "🐱名称Cat" ; @Override public void run () { System.out.println("🐱跑的飞快~~~~!" ); } }class Animal { public String name = "动物名称Animal" ; public void run () { System.out.println("动物跑!" ); } }
多态的概念: 同一个类型的对象,执行同一个行为,在不同的状态下会表现出不同的行为特征
多态的识别技巧:
对于方法的调用:编译看左边,运行看右边
对于变量的调用:编译看左边,运行看左边
多态的使用前提
必须存在继承或者实现关系
必须存在父类类型的变量引用子类类型的对象
需要存在方法重写
多态的优劣势 优势:
在多态形式下,右边对象可以实现组件化切换,业务功能也随之改变,便于扩展和维护。可以实现类与类之间的解耦
实际开发中,父类类型作为方法形式参数,传递之类对象给方法,可以传递一切子类对象进行方法的调用,更能体现出多态的扩展性与便利
劣势:
多态形式下,不能直接调用子类特有的功能 。编译看左边!!左边父类没有子类独有的功能,所以代码在编译阶段就直接报错了
引用类型自动类型转换 基本数据类型的转换
小范围类型的变量或者值可以直接赋值 给大范围类型的变量。
大范围类型的变量或者值必须强制类型转换 给小范围类型的变量。
所以引用类型转换的思想也一样
子类类型的对象或者变量可以自动类型转换赋值给父类类型的变量
引用类型的自动类型转换并不能解决多态的劣势
引用类型的强制类型转换 父类类型的变量或者对象必须强制类型转换成子类类型的变量,否则报错!
注意:有继承/实现关系 的两个类型就可以进行强制类型转换,编译阶段一定不报错!但是运行阶段可能出现:类型转换异常 ClassCastException
Java建议在进行强制类型转换之前先判断变量的真实类型,再强制类型转换!
变量 instanceof 类型
: 判断前面的变量是否是后面的类型或者其子类类型才会返回true
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 PolymorphicDemo { public static void main (String[] args) { Animal a = new Wolf (); a.run(); Wolf w = (Wolf) a; w.catchSheep(); Animal a1 = new Cat (); if (a1 instanceof Cat){ Cat c1 = (Cat) a1; c1.catchMouse(); }else if (a1 instanceof Wolf){ Wolf w1 = (Wolf) a1; w1.catchSheep(); } } }class Wolf extends Animal { @Override public void run () { System.out.println("狼跑的飞快~~~" ); } public void catchSheep () { System.out.println("🐺抓🐏" ); } }class Cat extends Animal { @Override public void run () { System.out.println("猫跑的贼快~~~" ); } public void catchMouse () { System.out.println("🐱抓🐀~~" ); } }class Animal { public void run () { 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 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 class Demo { public static void main (String[] args) { Computer c = new Computer (); USB xiaoMi = new Mouse ("小米鼠标" ); c.install(xiaoMi); KeyBoard sfy = new KeyBoard ("双飞燕键盘" ); c.install(sfy); } }class Computer { public void install (USB usb) { usb.connect(); if (usb instanceof Mouse){ Mouse m = (Mouse) usb; m.dbclick(); }else if (usb instanceof KeyBoard){ KeyBoard k = (KeyBoard) usb; k.keyDown(); } usb.unconnect(); } }class Mouse implements USB { private String name; public Mouse (String name) { this .name = name; } public void dbclick () { System.out.println(name+"双击了,老铁,6666666~~~~" ); } @Override public void connect () { System.out.println(name+"成功接入了设备~~~~" ); } @Override public void unconnect () { System.out.println(name+"成功拔出了设备~~~~" ); } }class KeyBoard implements USB { private String name; public KeyBoard (String name) { this .name = name; } public void keyDown () { System.out.println(name+"写下了,来了,老弟~~记得点亮小💗💗...." ); } @Override public void connect () { System.out.println(name+"成功接入了设备~~~~" ); } @Override public void unconnect () { System.out.println(name+"成功拔出了设备~~~~" ); } }interface USB { void connect () ; void unconnect () ; }
内部类 内部类是类的五大成分之一:成员变量、方法、构造器、代码块、内部类
内部类是定义在一个类里面的类
内部类有什么用
可以提供更好的封装性
内部类有更多的权限修饰符
其封装有更多的控制
可以体现出组件的思想
内部类的分类:
静态内部类
实例内部类(成员内部类)
局部内部类
匿名内部类
静态内部类 有static
修饰,属于外部类本身,会加载一次
成分研究:
类有的成分它都有,静态内部类属于外部类本身,只会加载一次
所以它的特点与外部类是完全一样的,只是位置在别人里面而已。
外部类=宿主
内部类=寄生
静态内部类的访问格式:
静态内部类创建对象的格式:
1 外部类名称.内部类名称 对象名称 = 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 class Outter { public static int age1 = 12 ; private double salary; public static class Inner { private String name; private int age; public static String schoolName = "黑马" ; public void show () { System.out.println(name+"-->" +age+"岁~" ); System.out.println(age1); } public Inner () { } public Inner (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; } } }
实例内部类(成员内部类) 无static
修饰的内部类,属于外部类的每个对象的,跟着对象一起加载的
实例内部类的成分特点:
实例内部类中不能定义静态成员,其他都可以定义
可以定义常量
实例内部类的访问格式:
创建对象的格式:
1 外部类名称.内部类名称 对象名称 = 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 public class InnerClass { public static void main (String[] args) { Outter.Inner in = new Outter ().new Inner (); in.show(); } }class Outter { public static int age = 1 ; private double salary; public class Inner { private String name ; public static final String schoolName = "黑马" ; public void show () { System.out.println(name+"名称!" ); System.out.println(age); System.out.println(salary); } public String getName () { return name; } public void setName (String name) { this .name = name; } } }
局部内部类 定义在方法中,在构造器中,代码块中,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 public class InnerClass { static { abstract class A { } } public static void main (String[] args) { class A { private String name; public void test () { } public String getName () { return name; } public void setName (String name) { this .name = name; } } A a = new A (); a.test(); } public static void test () { class Animal { } class Cat extends Animal { } } }
匿名内部类 就是一个没有名字的局部内部类
匿名内部类目的是为了:简化代码,也是开发中常用的形式
匿名内部类的格式:
1 2 3 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 public class Anonymity { public static void main (String[] args) { Animal a = new Animal (){ @Override public void run () { System.out.println("猫跑的贼溜~~" ); } }; a.run(); a.go(); Animal a1 = new Animal () { @Override public void run () { System.out.println("狗跑的贼快~~~" ); } }; a1.run(); a.go(); } }abstract class Animal { public abstract void run () ; public void go () { System.out.println("开始go~~~" ); } }
匿名内部类的使用形式
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 Anonymity02 { public static void main (String[] args) { Swim bozai = new Swim () { @Override public void swimming () { System.out.println("老师🏊的贼溜~~~~" ); } }; go(bozai); Swim boniu = new Swim () { @Override public void swimming () { System.out.println("波妞学生快乐的狗爬式~~~" ); } }; go(boniu); go(new Swim () { @Override public void swimming () { System.out.println("波妞2学生快乐的狗爬式~~~" ); } }); } public static void go (Swim s) { System.out.println("开始。。。。" ); s.swimming(); System.out.println("结束。。。。" ); } }interface Swim { void swimming () ; }
包和权限修饰符 包
分门别类的管理各种不同的技术。
企业的代码必须用包区分。便于管理技术,扩展技术,阅读技术。
定义包的格式:
package 包名;
必须放在类名的最上面 一般工具已经帮我们做好了
包名的命名规范:
注意
相同包下的类可以直接访问
不同包下的类必须导包,才可以使用
导包格式:import 包名.类名;
权限修饰符 权限修饰符:有四种(private
-> default
-> protected
- > public
)
可以修饰成员变量,修饰方法,修饰构造器,内部类,不同修饰符修饰的成员能够被访问的权限将受到限制!
Object类 Object
类是Java中的祖宗类
一个类要么默认继承了Object
类,要么间接继承了Object
类
Object
类的方法是一切子类都可以直接使用的,所以我们要学习Object
类的方法。
Object
类的常用方法:
开发中如果希望输出对象看到对象的内容,只需要重写toString()
方法即可
所以toString
方法存在的意义是为了被子类重写
public boolean equals(Object o)
默认是比较两个对象的地址是否相同。相同返回true
,反之
直接比较两个对象的地址是否相同完全可以用==
替代equals
,所以equals
存在的意义是为了被子类重写,以便程序员可以自己来定制比较规则
只要两个对象的内容一样,我们就认为他们是相等的。
equals
存在的意义是为了被子类重写,以便程序员自己来定制比较规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Override public boolean equals (Object o) { if (this == o) return true ; if (o == null || this .getClass() != o.getClass()) return false ; Student student = (Student) o; return age == student.age && sex == student.sex && Objects.equals(name, student.name); } @Override public String toString () { return "Student{" + "name='" + name + '\'' + ", age=" + age + ", sex=" + sex + '}' ; }
idea可以自动生成equals()
和toString()
,直接按下快捷键ALT+INSERT
,选择generate
即可
Objects类
Objects
类与Object
还是继承关系
Objects
类是从JDK 1.7开始之后才有的。
Objects
的方法:
Date类 Java是面向对象的,会用一个类代表一个事物
Date
类在Java中代表的是系统当前此刻日期时间对象。
Date
类:
包:java.util.Date
构造器:public Date()
创建当前系统的此刻日期时间对象。public Date(long time)
1 2 3 4 5 6 7 8 9 10 11 12 13 import java.util.Date;public class DateDemo01 { public static void main (String[] args) { Date d = new Date (); System.out.println(d); long time = d.getTime(); System.out.println(time); } }
时间记录的两种方式:Date
日期对象。 时间毫秒值:从1970-01-01 00:00:00开始走到此刻的总的毫秒值。 1s = 1000ms
小结:
Date
可以代表系统当前此刻日期时间对象。
时间记录的两种方式:
Date
日期对象。
时间毫秒值:从1970-01-01 00:00:00开始走到此刻的总的毫秒值。 1s = 1000ms
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import java.util.Date;public class DateDemo02 { public static void main (String[] args) { long startTime = new Date ().getTime(); for (int i = 1 ; i < 1000000 ; i++ ){ System.out.println("输出:" +i); } long endTime = new Date ().getTime(); System.out.println( (endTime - startTime) / 1000.0 +"s" ); } }
Date
类的有参数构造器的使用。
构造器:
public Date()
:创建当前系统的此刻日期时间对象。
public Date(long time)
:把时间毫秒值转换成日期对象。
流程
Date
日期对象 -> getTime()
-> 时间毫秒值
时间毫秒值 -> new Date(时间毫秒值)
-> Date
日期对象
day4-常用API、正则表达式、泛型、Collection集合API 简单日期格式化类SimpleDateFormat
可以把日期对象格式化成我们喜欢的时间形式
1 2 3 4 5 6 7 8 9 10 11 Date d = new Date (); System.out.println(d);SimpleDateFormat sdf = new SimpleDateFormat ("yyyy年MM月dd日 HH:mm:ss EEE a" );String rs = sdf.format(d); System.out.println(rs);
也可以直接格式化时间毫秒值
1 2 3 4 5 6 7 8 9 10 11 12 Date date = new Date (); System.out.println(date);long time = date.getTime(); time += 121 * 1000 ;SimpleDateFormat sdf = new SimpleDateFormat ("yyyy年MM月dd日 HH:mm:ss EEE a" ); System.out.println(sdf.format(time));
简单日期格式化类SimpleDateFormat
解析字符串时间成为日期对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 String date = "2019-11-04 09:30:30" ;SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss" );Date newDate = sdf.parse(date);long time = newDate.getTime() + (24L *60 *60 + 15 *60 *60 + 30 *60 + 29 ) * 1000 ; System.out.println(sdf.format(time));
第二章 Calendar类 Calendar
代表了系统此刻日期对应的日历对象。
Calendar
是一个抽象类,不能直接创建对象。
Calendar
日历类创建日历对象的语法:
1 Calendar rightNow = Calendar.getInstance();
Calendar
的方法:
public static Calendar getInstance()
: 返回一个日历类的对象。
public int get(int field)
:取日期中的某个字段信息。
public void set(int field,int value)
:修改日历的某个字段信息。
public void add(int field,int amount)
:为某个字段增加/减少指定的值
public final Date getTime()
: 拿到此刻日期对象。
public long getTimeInMillis()
: 拿到此刻时间毫秒值
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 Calendar rightNow = Calendar.getInstance(); System.out.println(rightNow);int year = rightNow.get(Calendar.YEAR); System.out.println(year);int mm = rightNow.get(Calendar.MONTH) + 1 ; System.out.println(mm);int days = rightNow.get(Calendar.DAY_OF_YEAR); System.out.println(days);Date d = rightNow.getTime(); System.out.println(d);long time = rightNow.getTimeInMillis(); System.out.println(time); rightNow.add(Calendar.DAY_OF_YEAR , 701 ); rightNow.add(Calendar.HOUR , 15 );long time1 = rightNow.getTimeInMillis();SimpleDateFormat sdf = new SimpleDateFormat ("yyyy年MM月dd日 HH:mm:ss EEE a" ); System.out.println(sdf.format(time1));
第三章 Math类
Math
用于做数学运算
Math
类中的方法全部是静态方法,直接用类名调用即可。
常用方法
public static int abs(int a)
获取参数a的绝对值:
public static double ceil(double a)
向上取整
public static double floor(double a)
向下取整
public static double pow(double a, double b)
获取a的b次幂
public static long round(double a)
四舍五入取整
第四章 System类 System
系统类的使用,System
代表当前系统
静态方法:
public static void exit(int status)
:终止JVM虚拟机,非0是异常终止。
public static long currentTimeMillis()
:获取当前系统此刻时间毫秒值。
可以做数组的拷贝。arraycopy(Object var0, int var1, Object var2, int var3, int var4);
参数一:原数组
参数二:从原数组的哪个位置开始赋值。
参数三:目标数组
参数四:赋值到目标数组的哪个位置
参数五:赋值几个。
第五章 BigDecimal类 BigDecimal
大数据类
浮点型运算的时候直接+ * / 可能会出现数据失真(精度问题)
BigDecimal
可以解决浮点型运算数据失真的问题
包:java.math.
创建对象的方式(最好的方式:) public static BigDecimal valueOf(double val)
:包装浮点数成为大数据对象。 方法声明
public BigDecimal add(BigDecimal value)
加法运算
public BigDecimal subtract(BigDecimal value)
减法运算
public BigDecimal multiply(BigDecimal value)
乘法运算
public BigDecimal divide(BigDecimal value)
除法运算
public double doubleValue()
把BigDecimal
转换成double
类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 double a = 0.1 ;double b = 0.2 ;BigDecimal a1 = BigDecimal.valueOf(a);BigDecimal b1 = BigDecimal.valueOf(b);BigDecimal c1 = a1.divide(b1); System.out.println(c1);double rs = c1.doubleValue(); System.out.println(rs);
第六章 包装类 Java
认为一切皆对象。引用数据类型就是对象了
但是在Java中8
基本数据类型不是对象,只是表示一种数据的类型形式,这8种数据类型显得很突兀
Java
为了一切皆对象的思想统一,把8种基本数据类型转换成对应的类,这个类称为基本数据类型的包装类。
基本数据类型 包装类(引用数据类型) byte Byte short Short int Integer(特殊) long Long
float Float double Double char Character(特殊) boolean Boolean
自动装箱:可以直接把基本数据类型的值或者变量赋值给包装类
自动拆箱:可以把包装类的变量直接赋值给基本数据类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int a = 12 ;Integer a1 = 12 ; Integer a2 = a ; double b = 99.9 ;Double b1 = 99.9 ; Double b2 = b ; Integer c = 100 ;int c1 = c ; int d = 12 ;Integer d1 = null ; Integer d2 = 0 ; System.out.println("-----------------" );Integer it = Integer.valueOf(12 ); Integer it2 = 12 ;Integer it3 = 111 ;int it33 = it3.intValue(); int it333 = it3;
第七章 正则表达式 是一些特殊字符组成的校验规则,可以校验信息的正确性,校验邮箱是否合法,例如电话号码,金额等。
字符类
1 2 3 4 5 6 7 [abc] a 、b 或 c(简单类)[^abc] 任何字符,除了 a 、b 或 c(否定)[a-zA-Z] a 到 z 或 A 到 Z,两头的字母包括在内(范围)[a-d[m-p] ] a 到 d 或 m 到 p :[a-dm-p] (并集)[a-z&&[def23] ] d、e 或 f(交集)[a-z&&[^bc] ] a 到 z,除了 b 和 c:[ad-z] (减去)[a-z&&[^m-p] ] a 到 z,而非 m 到 p :[a-lq-z] (减去)
预定义字符类
1 2 3 4 5 6 7 . 任何字符\d 数字:[0 -9 ]\D 非数字: [^0 -9 ]\s 空白字符:[ \t\n\x0B\f\r] \S 非空白字符:[^\s] \w 单词字符:[a-zA-Z_0-9 ]\W 非单词字符:[^\w]
以上正则匹配只能校验单个字符。
Greedy 数量词
1 2 3 4 5 6 X ? X ,一次或一次也没有X * X ,零次或多次X + X ,一次或多次X {n} X ,恰好 n 次X {n,} X ,至少 n 次X {n,m} X ,至少 n 次,但是不超过 m 次
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 private static void checkPhone () { Scanner sc = new Scanner (System.in); System.out.print("请您输入电话号码:" ); String phone = sc.nextLine(); if (phone.matches("0\\d{2,5}-?\\d{5,15}" )){ System.out.println("电话号码合法了!" ); }else { System.err.println("电话号码不正确!" ); } } private static void checkTel () { Scanner sc = new Scanner (System.in); System.out.print("请您输入手机号码:" ); String tel = sc.nextLine(); if (tel.matches("1[3-9]\\d{9}" )){ System.out.println("手机号码合法了!" ); }else { System.err.println("手机号码不正确!" ); } } public static void checkEmail () { Scanner sc = new Scanner (System.in); System.out.print("请您输入邮箱:" ); String email = sc.nextLine(); if (email.matches("\\w{1,}@\\w{2,10}(\\.\\w{2,10}){1,2}" )){ System.out.println("邮箱合法了!" ); }else { System.err.println("邮箱格式不正确!" ); } }
split
可以结合正则表达式分割
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 String names = "贾乃亮,王宝强,陈羽凡" ; String[] nameArrs = names.split("," );for (int i = 0 ; i < nameArrs.length ; i++ ){ String name = nameArrs[i]; System.out.println(name); } System.out.println("----------------------" );String names1 = "贾乃亮lv434fda324王宝强87632fad2342423陈羽凡" ; String[] nameArrs1 = names1.split("\\w+" );for (int i = 0 ; i < nameArrs1.length ; i++ ){ String name = nameArrs1[i]; System.out.println(name); } System.out.println("----------------------" );String names2 = "贾乃亮lv434fda324王宝强87632fad2342423陈羽凡" ; System.out.println(names2.replaceAll("\\w+" , "/" ));String names3 = "贾乃亮,王宝强,羽凡" ; System.out.println(names3.replaceAll("," ,"-" ));
正则表达式爬取信息中的内容
可以通过|
将正则表达式连接起来,是或 的关系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 String rs = "来黑马程序学习Java,电话020-43422424,或者联系邮箱" + "itcast@itcast.cn,电话18762832633,0203232323" + "邮箱bozai@itcast.cn,400-100-3233 ,4001003232" ;String regex = "(\\w{1,}@\\w{2,10}(\\.\\w{2,10}){1,2})|(1[3-9]\\d{9})|(0\\d{2,5}-?\\d{5,15})|400-?\\d{3,8}-?\\d{3,8}" ;Pattern pattern = Pattern.compile(regex);Matcher matcher = pattern.matcher(rs);while (matcher.find()){ System.out.println(matcher.group()); }
第八章 泛型 泛型概念 什么是泛型
泛型就是一个标签:<数据类型>
泛型可以在编译阶段约束只能操作某种数据类型
JDK 1.7开始之后,泛型后面的申明可以省略不写
泛型和集合都只能支持引用数据类型,不支持基本数据类型
泛型的好处
泛型在编译阶段约束了操作的数据类型,从而不会出现类型转换异常
体现的是Java的严谨性和规范性,数据类型,经常需要进行统一
自定义泛型 使用了泛型定义的类就是泛型类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class MyArrayList <E>{ private ArrayList lists = new ArrayList (); public void add (E e) { lists.add(e); } public void remove (E e) { lists.remove(e); } @Override public String toString () { return lists.toString(); } }
自定义泛型方法 定义了泛型的方法就是泛型方法
泛型方法定义格式
1 2 3 修饰符 <泛型变量> 返回值类型 方法名称(形参列表){ }
一个泛型方法的例子
1 2 3 4 5 6 7 8 9 10 11 12 public static <T> String arrToString (T[] nums) { StringBuilder sb = new StringBuilder (); sb.append("[" ); if (nums!=null && nums.length > 0 ){ for (int i = 0 ; i < nums.length ; i++ ){ T ele = nums[i]; sb.append(i == nums.length-1 ? ele : ele+", " ); } } sb.append("]" ); return sb.toString(); }
泛型方法是一个通用技术
泛型接口 使用了泛型定义的接口就是泛型接口
定义格式
1 2 3 修饰符 interface 接口名称<泛型变量>{ }
例如
1 2 3 4 5 6 public interface Data <E> { void add (E stu) ; void delete (E stu) ; void update (E stu) ; E query (int id) ; }
泛型接口的核心思想,在实现接口的时候传入真实的数据类型
这样重写的方法就是对该数据类型进行操作
泛型的通配符 通配符:?
?
可以用在使用泛型 的时候代表一切类型
E , T , K , V
是在定义泛型 的时候使用代表一切类型
泛型的上下限:
? extends Car
: 那么?
必须是Car
或者其子类。(泛型的上限)
? super Car
:那么?
必须是Car
或者其父类。(泛型的下限。不是很常见)
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 GenericDemo { public static void main (String[] args) { ArrayList<BMW> bmws = new ArrayList <>(); bmws.add(new BMW ()); bmws.add(new BMW ()); bmws.add(new BMW ()); run(bmws); ArrayList<BENZ> benzs = new ArrayList <>(); benzs.add(new BENZ ()); benzs.add(new BENZ ()); benzs.add(new BENZ ()); run(benzs); ArrayList<Dog> dogs = new ArrayList <>(); dogs.add(new Dog ()); dogs.add(new Dog ()); dogs.add(new Dog ()); } public static void run (ArrayList<? extends Car> cars) { } }class Car { }class BMW extends Car { }class BENZ extends Car { }class Dog { }
第九章 Collection集合 集合概述 什么是集合
集合是一个大小可变的容器
容器中的每个数据称为一个元素。数据==元素
集合的特点是:类型可以不确定,大小不固定。集合有很多种,不同的集合特点和使用场景不同
数组:类型和长度一旦定义出来就都固定了
集合用处
在开发中,很多时候元素的个数是不确定的。
而且经常要进行元素的增删该查操作,集合都是非常合适的。
开发中集合用的更多
Java中集合的代表是:Collection
Collection
集合是Java中集合的祖宗类
学习Collection集合的功能,那么一切集合都可以用这些功能
集合体系
1 2 3 4 5 6 7 Collection<E > (接口) / \ Set<E > (接口) List<E > (接口) / \ / \ HashSet<E > (实现类) TreeSet<> (实现类) ArrayList<E > (实现类) LinekdList<> (实现类) / LinkedHashSet<> (实现类)
集合的特点:
Set
系列集合:添加的元素是无序,不重复,无索引的
HashSet
: 添加的元素是无序,不重复,无索引的
LinkedHashSet
: 添加的元素是有序,不重复,无索引的
TreeSet
: 不重复,无索引,按照大小默认升序排序
List
系列集合:添加的元素是有序,可重复,有索引
ArrayList
:添加的元素是有序,可重复,有索引
LinekdList
:添加的元素是有序,可重复,有索引
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Collection<String> sets = new HashSet <>(); sets.add("MyBatis" ); sets.add("Java" ); sets.add("Java" ); sets.add("Spring" ); sets.add("MySQL" ); sets.add("MySQL" ); System.out.println(sets); Collection<String> lists = new ArrayList <>(); lists.add("MyBatis" ); lists.add("Java" ); lists.add("Java" ); lists.add("Spring" ); lists.add("MySQL" ); lists.add("MySQL" ); System.out.println(lists);
集合常用API Collection
是集合的祖宗类,它的功能是全部集合都可以继承使用的,所以要学习它。 Collection API如下:
public boolean add(E e)
: 把给定的对象添加到当前集合中 。
public void clear()
:清空集合中所有的元素。
public boolean remove(E e)
: 把给定的对象在当前集合中删除。
public boolean contains(Object obj)
: 判断当前集合中是否包含给定的对象。
public boolean isEmpty()
: 判断当前集合是否为空。
public int size()
: 返回集合中元素的个数。
public Object[] toArray()
: 把集合中的元素,存储到数组中
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 Collection<String> sets = new HashSet <>(); System.out.println(sets.add("贾乃亮" )); System.out.println(sets.add("贾乃亮" )); System.out.println(sets.add("王宝强" )); sets.add("陈羽凡" ); System.out.println(sets); System.out.println(sets.isEmpty()); System.out.println(sets.size()); System.out.println(sets.contains("贾乃亮" )); sets.remove("陈羽凡" ); System.out.println(sets); Object[] arrs = sets.toArray(); System.out.println("数组:" + Arrays.toString(arrs)); String[] arrs1 = sets.toArray(String[]::new ); System.out.println("数组:" + Arrays.toString(arrs1)); System.out.println("---------------------拓展---------------------------" ); Collection<String> c1 = new ArrayList <>(); c1.add("李小璐" ); c1.add("马蓉" ); Collection<String> c2 = new ArrayList <>(); c2.add("白百合" ); c1.addAll(c2); System.out.println(c1);
day5-迭代器、数据结构、List、Set、TreeSet集合、Collections工具类 第一章 Iterator迭代器 Collection
集合的遍历方式
迭代器遍历
public Iterator iterator()
获取集合对应的迭代器,用来遍历集合中的元素
E next()
获取下一个元素值
boolean hasNext()
判断是否有下一个元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Collection<String> lists = new ArrayList <>(); lists.add("赵敏" ); lists.add("小昭" ); lists.add("殷素素" ); lists.add("周芷若" ); System.out.println(lists); Iterator<String> it = lists.iterator();while (it.hasNext()){ String ele = it.next(); System.out.println(ele); }
for-each遍历 for-each
遍历实际上是迭代器遍历的简化写法
for-each
遍历集合或者数组很方便
for-each
遍历无法知道遍历到了哪个元素,因为没有索引
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Collection<String> lists = new ArrayList <>(); lists.add("赵敏" ); lists.add("小昭" ); lists.add("殷素素" ); lists.add("周芷若" ); System.out.println(lists);for (String ele : lists) { System.out.println(ele); }int [] ages = new int []{17 , 18 , 38 , 21 };for (int age : ages) { System.out.println(age); }
lambda表达式 1 2 3 4 5 6 7 8 9 10 11 12 13 Collection<String> lists = new ArrayList <>(); lists.add("赵敏" ); lists.add("小昭" ); lists.add("殷素素" ); lists.add("周芷若" ); System.out.println(lists); lists.forEach(s -> { System.out.println(s); });
第二章 Java常见数据结构种类 队列(queue)
先进先出,后进后出。
场景:各种排队。叫号系统。
有很多集合可以实现队列。
栈(stack)
后进先出,先进后出 压栈 == 入栈 弹栈 == 出栈 场景:手枪的弹夹。
数组
数组是内存中的连续存储区域。
分成若干等分的小区域(每个区域大小是一样的)
元素存在索引
特点:查询元素快(根据索引快速计算出元素的地址,然后立即去定位) 增删元素慢(创建新数组,迁移元素)
链表
元素不是内存中的连续区域存储。
元素是游离存储的。每个元素会记录下个元素的地址。
特点:查询元素慢 增删元素快(针对于首尾元素,速度极快,一般是双链表)
红黑树
二叉树:binary tree 永远只有一个根节点,是每个结点不超过2个节点的树(tree) 。
查找二叉树,排序二叉树:小的左边,大的右边,但是可能树很高,性能变差。
为了做排序和搜索会进行左旋和右旋实现平衡查找二叉树,让树的高度差不大于1
红黑树(就是基于红黑规则实现了自平衡的排序二叉树):树尽量的保证到了很矮小,但是又排好序了,性能最高的树。
红黑树的增删查改性能都好!!!
这些结构,其实Java早就通过代码实现了,我们要知道有这些结构即可!
第三章 List系列集合的使用 ArrayList集合
Collection
集合体系的特点:
Set
系列集合:添加的元素,是无序,不重复,无索引的。
HashSet
:添加的元素,是无序,不重复,无索引的。
LinkedHashSet
:添加的元素,是有序,不重复,无索引的。
List
系列集合:添加的元素,是有序,可重复,有索引的。
LinkedList
: 添加的元素,是有序,可重复,有索引的。
ArrayList
: 添加的元素,是有序,可重复,有索引的。
Vector
:是线程安全的,速度慢,工作中很少使用。
List
集合继承了Collection
集合的全部功能,同时因为List
系列集合有索引,
因为List
集合多了索引,所以多了很多按照索引操作元素的功能
ArrayList
实现类集合底层基于数组存储数据的,查询快,增删慢!
public void add(int index, E element)
: 将指定的元素,添加到该集合中的指定位置上。
public E get(int index)
:返回集合中指定位置的元素。
public E remove(int index)
: 移除列表中指定位置的元素, 返回的是被移除的元素。
public E set(int index, E element)
:用指定元素替换集合中指定位置的元素,返回更新前的元素值。
List
系列集合有序,可重复,有索引的。
ArrayList
实现类集合底层基于数组存储数据的,查询快,增删慢!!
开发中ArrayList
集合用的最多!!
List集合的遍历方式 List
遍历方式有四种:
for
循环
迭代器
for-each
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 List<String> lists = new ArrayList <>(); lists.add("java1" ); lists.add("java2" ); lists.add("java3" );for (int i = 0 ; i < lists.size() ; i++ ) { String ele = lists.get(i); System.out.println(ele); } System.out.println("-----------------------" ); Iterator<String> it = lists.iterator();while (it.hasNext()){ System.out.println(it.next()); } System.out.println("-----------------------" );for (String ele : lists){ System.out.println(ele); } System.out.println("-----------------------" ); lists.forEach(s -> { System.out.println(s); });
LinkedList集合 LinkedList
也是List
的实现类:底层是基于链表的,增删比较快,查询慢!!LinkedList
是支持双链表,定位前后的元素是非常快的,增删首尾的元素也是最快的 所以LinkedList
除了拥有List
集合的全部功能还多了很多操作首尾元素的特殊功能:
public void addFirst(E e)
:将指定元素插入此列表的开头。
void addLast(E e)
:将指定元素添加到此列表的结尾。
public E getFirst()
:返回此列表的第一个元素。
public E getLast()
:返回此列表的最后一个元素。
public E removeFirst()
:移除并返回此列表的第一个元素。
public E removeLast()
:移除并返回此列表的最后一个元素。
public E pop()
:从此列表所表示的堆栈处弹出一个元素。
public void push(E e)
:将元素推入此列表所表示的堆栈。
LinkedList
是支持双链表,定位前后的元素是非常快的,增删首尾的元素也是最快的。
所以提供了很多操作首尾元素的特殊API可以做栈和队列的实现。
如果查询多而增删少用ArrayList
集合。(用的最多的)
如果查询少而增删首尾较多用LinkedList
集合。
第四章 Set系列集合 研究两个问题==面试热点==
Set
集合添加的元素是不重复的,是如何去重复的
Set
集合元素无序的原因是什么
Set系列集合元素去重复的流程
对于有值特性的,Set
集合可以直接判断进行去重复。
对于引用数据类型的类对象,Set集合是按照如下流程进行是否重复的判断。
Set
集合会让两两对象,先调用自己的hashCode()
方法得到彼此的哈希值(所谓的内存地址)
然后比较两个对象的哈希值是否相同,如果不相同则直接认为两个对象不重复。
如果哈希值相同,会继续让两个对象进行equals
比较内容是否相同,如果相同认为真的重复了
如果不相同认为不重复。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Set<Integer> sets = new HashSet <>(); sets.add(1 ); sets.add(1 ); sets.add(2 ); sets.add(2 ); System.out.println(sets); Set<Apple> apples = new HashSet <>();Apple a1 = new Apple ("红富士" ,59.9 ,"红色" );Apple a2 = new Apple ("阿克苏" ,39.9 ,"青红色" );Apple a3 = new Apple ("阿克苏" ,39.9 ,"青红色" ); System.out.println(a1.hashCode()); System.out.println(a2.hashCode()); System.out.println(a3.hashCode()); apples.add(a1); apples.add(a2); apples.add(a3); System.out.println(apples);
Set系列集合元素无序==面试必考== Set
系列集合添加元素无序的根本原因是因为底层采用了哈希表存储元素。
JDK 1.8之前:哈希表 = 数组 + 链表 + (哈希算法)
JDK 1.8之后:哈希表 = 数组 + 链表 + 红黑树 + (哈希算法)
当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
LinkedHashSet 是HashSet
的子类,元素是“有序” 不重复,无索引.
LinkedHashSet
底层依然是使用哈希表存储元素的,
但是每个元素都额外带一个链来维护添加顺序!!
不光增删查快,还有序。缺点是多了一个存储顺序的链会占内存空间!!而且不允许重复,无索引。
如果希望元素可以重复,又有索引,查询要快用ArrayList
集合。(用的最多)
如果希望元素可以重复,又有索引,增删要快要用LinkedList
集合。(适合查询元素比较少的情况,经常要首尾操作元素的情况)
如果希望增删改查都很快,但是元素不重复以及无序无索引,那么用HashSet
集合。
如果希望增删改查都很快且有序,但是元素不重复以及无索引,那么用LinkedHashSet
集合。
TreeSet集合 TreeSet
: 不重复,无索引,按照大小默认升序排序!!
TreeSet
集合称为排序不重复集合,可以对元素进行默认的升序排序。
TreeSet
集合自自排序的方式:
有值特性的元素直接可以升序排序。(浮点型,整型)
字符串类型的元素会按照首字符的编号排序。
对于自定义的引用数据类型,TreeSet
默认无法排序,执行的时候直接报错,因为人家不知道排序规则。
自定义的引用数据类型的排序实现:
对于自定义的引用数据类型,TreeSet
默认无法排序
所以我们需要定制排序的大小规则,程序员定义大小规则的方案有2种:
直接为对象的类实现比较器规则接口Comparable,重写比较方法(拓展方式) // 如果程序员认为比较者大于被比较者 返回正数! // 如果程序员认为比较者小于被比较者 返回负数! // 如果程序员认为比较者等于被比较者 返回0!
直接为集合设置比较器Comparator对象,重写比较方法 // 如果程序员认为比较者大于被比较者 返回正数! // 如果程序员认为比较者小于被比较者 返回负数! // 如果程序员认为比较者等于被比较者 返回0!
如果类和集合都带有比较规则,优先使用集合自带的比较规则。
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 Set<Double> scores = new TreeSet <>(); scores.add(100.0 ); scores.add(99.9 ); scores.add(69.5 ); scores.add(0.1 ); scores.add(89.3 ); System.out.println(scores); Set<String> names = new TreeSet <>(); names.add("Jack" ); names.add("rose" ); names.add("Dlei" ); names.add("about" ); names.add("曹雪芹" ); names.add("bozai" ); names.add("caocao" ); names.add("angel" ); System.out.println(names); Set<Employee> employees = new TreeSet <>(); employees.add(new Employee ("播仔" ,6500.0 ,21 )); employees.add(new Employee ("播妞" ,7500.0 ,19 )); employees.add(new Employee ("乔治" ,4500.0 ,23 )); System.out.println(employees); Set<Employee> employees1 = new TreeSet <>(new Comparator <Employee>() { @Override public int compare (Employee o1, Employee o2) { return o1.getAge() - o2.getAge(); } }); employees1.add(new Employee ("播仔" ,6500.0 ,21 )); employees1.add(new Employee ("播妞" ,7500.0 ,19 )); employees1.add(new Employee ("乔治" ,4500.0 ,23 )); System.out.println(employees1);
第五章 Collections工具类 Collections
并不属于集合,而是用来操作集合的工具类
Collections
有几个常用的API:
public static <T> boolean addAll(Collection<? super T> c, T... elements)
给集合对象批量添加元素!
public static void shuffle(List<?> list)
打乱集合顺序。
public static <T> void sort(List<T> list)
将集合中元素按照默认规则排序。
public static <T> void sort(List<T> list,Comparator<? super T> )
将集合中元素按照指定规则排序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Collection<String> names = new ArrayList <>(); Collections.addAll(names,"曹操" ,"贾乃亮" ,"王宝强" ,"陈羽凡" ); System.out.println(names); List<String> newnames = new ArrayList <>(); Collections.addAll(newnames,"曹操" ,"贾乃亮" ,"王宝强" ,"陈羽凡" ); Collections.shuffle(newnames); System.out.println(newnames); List<Double> scores = new ArrayList <>(); Collections.addAll(scores, 98.5 , 66.5 , 59.5 , 66.5 , 99.5 ); Collections.sort(scores); System.out.println(scores);
引用类型的排序
字符串按照首字符的编号升序排序!
自定义类型的比较方法API:
public static <T> void sort(List<T> list)
集合中元素按照默认规则排序。
对于自定义的引用类型的排序人家根本不知道怎么排,直接报错!
如果希望自定义的引用类型排序不报错,可以给类提供比较规则:Comparable。
public static <T> void sort(List<T> list,Comparator<? super T> c)
将集合中元素按照指定规则排序,自带比较器
注意:如果类有比较规则,而这里有比较器,优先使用比较器。
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 List<Orange> oranges = new ArrayList <>();Orange o1 = new Orange ("红橘子" ,654.0 ,"贼便宜~" );Orange o2 = new Orange ("黄橘子" ,454.0 ,"贼便宜~" );Orange o3 = new Orange ("黄橘子" ,454.0 ,"贼便宜~" );Orange o4 = new Orange ("青橘子" ,456.0 ,"贼便宜~" ); Collections.addAll(oranges,o1,o2,o3,o4); Collections.sort(oranges); System.out.println(oranges); List<Orange> oranges1 = new ArrayList <>();Orange o11 = new Orange ("红橘子" ,654.0 ,"贼便宜~" );Orange o22 = new Orange ("黄橘子" ,454.0 ,"贼便宜~" );Orange o33 = new Orange ("黄橘子" ,454.0 ,"贼便宜~" );Orange o44 = new Orange ("青橘子" ,456.0 ,"贼便宜~" ); Collections.addAll(oranges1,o11,o22,o33,o44); Collections.sort(oranges1, new Comparator <Orange>() { @Override public int compare (Orange o1, Orange o2) { if (o1.getWeight() > o2.getWeight()) return -1 ; if (o1.getWeight() < o2.getWeight()) return 1 ; return 0 ; } }); System.out.println(oranges1);
第六章 可变参数 可变参数用在形参中可以接收多个数据。
可变参数的格式:数据类型… 参数名称
可变参数的作用:
传输参数非常灵活,方便。
可以不传输参数。
可以传输一个参数。
可以传输多个参数。
可以传输一个数组。
可变参数在方法内部本质上就是一个数组。
可变参数的注意事项:
一个形参列表中可变参数只能有一个!!
可变参数必须放在形参列表的最后面!!
1 2 3 4 5 6 7 8 9 10 11 12 13 public static void main (String[] args) { sum(); sum(10 ); sum(10 ,20 ,30 ); sum(new int []{10 ,30 ,50 ,70 ,90 }); }public static void sum (int ...nums) { System.out.println("元素个数:" +nums.length); System.out.println("元素内容:" + Arrays.toString(nums)); System.out.println("--------------------------" ); }
day6-Map 第一章 Map Map集合的概述 Map
集合是另一个集合体系。
Collection
是单值集合体系。
Map
集合是一种双列集合,每个元素包含两个值。
Map
集合的每个元素的格式:key=value
(键值对元素)。
Map
集合也被称为“键值对集合”。
Map
集合的完整格式:{key1=value1 , key2=value2 , key3=value3 , ...}
Map
集合的特点都是由键决定的。
Map
集合的键是无序,不重复的,无索引的,Map
集合后面重复的键对应的元素会覆盖前面的整个元素!
Map
集合的值无要求。
Map
集合的键值对都可以为null
。
HashMap
:元素按照键是无序,不重复,无索引,值不做要求。LinkedHashMap
:元素按照键是有序,不重复,无索引,值不做要求。
Map集合的API
public V put(K key, V value)
: 把指定的键与指定的值添加到Map
集合中。
public V remove(Object key)
: 把指定的键 所对应的键值对元素 在Map
集合中删除,返回被删除元素的值。
public V get(Object key)
根据指定的键,在Map
集合中获取对应的值。
public Set<K> keySet()
: 获取Map
集合中所有的键,存储到Set
集合中。
public Set<Map.Entry<K,V>> entrySet()
: 获取到Map
集合中所有的键值对对象的集合(Set
集合)。
public boolean containKey(Object key)
:判断该集合中是否有此键。
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 Map<String , Integer> maps = new HashMap <>(); maps.put("iphoneX" ,10 ); maps.put("娃娃" ,30 ); maps.put("iphoneX" ,100 ); maps.put("huawei" ,1000 ); maps.put("生活用品" ,10 ); maps.put("手表" ,10 ); System.out.println(maps); System.out.println(maps.isEmpty()); System.out.println(maps.get("娃娃" )); maps.remove("iphoneX" ); System.out.println(maps); System.out.println(maps.containsKey("手表" )); System.out.println(maps.containsKey(10 )); System.out.println(maps.containsValue(1000 )); System.out.println(maps.containsValue(10 )); System.out.println(maps.containsValue("30" )); Set<String> keys = maps.keySet();for (String key : keys) { System.out.println(key); } Collection<Integer> values = maps.values();for (Integer value : values) { System.out.println(value); } System.out.println(maps.size()); Map<String,Integer> maps2 = new HashMap <>(); maps2.put("xiaoMi" , 1 ); maps2.put("🔨手机" , 10 ); maps2.put("手表" , 10000 ); maps.putAll(maps2); System.out.println(maps);
Map集合的遍历 Map
集合的遍历方式有3种:
键找值 的方式遍历:先获取Map
集合全部的键,再根据遍历键找值
键值对 的方式遍历
JDK1.8之后支持Lambda
表达式
首先是键找值 遍历方式
先获取Map
集合的全部键的Set
集合
遍历键的Set
集合,然后通过键找值
1 2 3 4 5 6 Set<String> keys = maps.keySet();for (String key : keys) { Integer value = maps.get(key); System.out.println(key + "=" + value); }
然后是键值对 的方式遍历
把Map
集合转换成一个Set
集合:Set<Map.Entry<K, V>> entrySet();
此时键值对元素的类型就确定了,类型是键值对实体类型:Map.Entry<K, V>
接下来就可以用foreach
遍历这个Set
集合,类型用Map.Entry<K, V>
1 2 3 4 5 6 Set<Map.Entry<String,Integer>> entries = maps.entrySet();for (Map.Entry<String, Integer> entry : entries) { String key = entry.getKey(); Integer value = entry.getValue(); System.out.println(key + "=>" + value); }
最后是简介的Lambda
表达式
1 2 3 maps.forEach((k , v) -> { System.out.println(k+"==>" +v); });
Map集合存储自定义类型 Map
集合的键和值都可以存储自定义类型
如果Map
集合认为自定义类型的键对象重复了,必须重写对象的hashCode()
和equals()
方法
Orange.java
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 public class Orange { private String name; private double weight; private String price; public Orange () { } public Orange (String name, double weight, String price) { this .name = name; this .weight = weight; this .price = price; } @Override public boolean equals (Object o) { if (this == o) return true ; if (o == null || getClass() != o.getClass()) return false ; Orange orange = (Orange) o; return Double.compare(orange.weight, weight) == 0 && Objects.equals(name, orange.name) && Objects.equals(price, orange.price); } @Override public int hashCode () { return Objects.hash(name, weight, price); } }
Map
使用自定义存储类型
1 2 3 4 5 6 7 8 9 10 11 12 Map<Orange,String> maps = new HashMap <>();Orange o1 = new Orange ("黄橘子" ,20.3 , "贼便宜!" );Orange o2 = new Orange ("黑橘子" ,30.3 , "坏了" );Orange o3 = new Orange ("青橘子" ,34.3 , "9.9包邮" );Orange o4 = new Orange ("黄橘子" ,20.3 , "贼便宜!" ); maps.put(o1 , "江西\n" ); maps.put(o2 , "赣州\n" ); maps.put(o3 , "广州\n" ); maps.put(o4 , "广西\n" ); System.out.println(maps);
LinkedHashMap的特点 LinkedHashMap
是HashMap
的子类,添加的元素按照键有序,不重复的。
HashSet
集合相当于是HashMap
集合的键都不带值。
LinkedHashSet
集合相当于是LinkedHashMap
集合的键都不带值。
底层原理完全一样,都是基于哈希表按照键存储数据的,
只是HashMap
或者LinkedHashMap
的键都多一个附属值。
1 2 3 4 5 6 7 8 Map<String , Integer> maps = new LinkedHashMap <>(); maps.put("iphoneX" ,10 ); maps.put("娃娃" ,30 ); maps.put("iphoneX" ,100 ); maps.put("huawei" ,1000 ); maps.put("生活用品" ,10 ); maps.put("手表" ,10 ); System.out.println(maps);
HashMap
集合是无序不重复的键值对集合。
LinkedHashMap
集合是有序不重复的键值对集合。
他们都是基于哈希表存储数据,增删改查都很好。
TreeMap集合应用
TreeMap
集合按照键是可排序不重复的键值对集合。(默认升序)
TreeMap
集合按照键排序的特点与TreeSet
是完全一样的
TreeMap
集合和TreeSet
集合都是排序不重复集合
TreeSet
集合的底层是基于TreeMap
,只是键没有附属值而已。
所以TreeMap
集合指定大小规则有2种方式:
直接为对象的类实现比较器规则接口Comparable
,重写比较方法(拓展方式)
直接为集合设置比较器Comparator
对象,重写比较方法
类实现Comparable
1 2 3 4 5 6 7 8 9 @Override public int compareTo (Object o) { return -Double.compare(this .price , ((Pig)o).price); }
集合设置Comparator
1 2 3 4 5 6 Map<Pig,String> pigs1 = new TreeMap <>(new Comparator <Pig>() { @Override public int compare (Pig p1, Pig p2) { return Double.compare(p1.getWeight() , p2.getWeight()); } });
第二章 排序算法 冒泡排序算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int [] arr = new int [] {55 , 22 , 99 , 88 };for (int i = 0 ; i < arr.length - 1 ; i++ ){ for (int j = 0 ; j < arr.length - i - 1 ; j++ ){ if (arr[j] > arr[j+1 ]){ int temp = arr[j+1 ]; arr[j+1 ] = arr[j]; arr[j] = temp; } } }
选择排序算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int [] arr = {5 , 1 , 3 , 2 };for (int i = 0 ; i < arr.length - 1 ; i++ ){ for (int j = i+1 ; j < arr.length ; j++ ){ if (arr[j] < arr[i]){ int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } }
第三章 二分查找 二分查找的前提 :对数组是有要求的,数组必须已经排好序。
每次先与中间的元素进行比较,如果大于往右边找,如果小于往左边找,如果等于就返回该元素索引位置!
如果没有该元素,返回-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 public static int binarySerach (int [] arr , int number) { int start = 0 ; int end = arr.length - 1 ; while (start <= end){ int middleIndex = (start + end) / 2 ; if (number < arr[middleIndex]){ end = middleIndex - 1 ; }else if (number > arr[middleIndex]){ start = middleIndex + 1 ; }else if (number == arr[middleIndex]){ return middleIndex; } } return -1 ; }
day7-异常、线程 第一章 异常 异常的概述和体系 异常 :指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止
在Java
等面向对象的编程语言中,异常本身是一个类,产生异常就是创建异常对象并抛出了一个异常对象。Java
处理异常的方式是中断处理。
Java
会为常见的代码异常都设计一个类来代表
Java
中异常继承的根类是:Throwable
Error
: 错误的意思,严重错误Error
,无法通过处理的错误,一旦出现,程序员无能为力了,
只能重启系统,优化项目。
比如内存奔溃,JVM
本身的奔溃。这个程序员无需理会。
Exception
:才是异常类,它才是开发中代码在编译或者执行的过程中可能出现的错误,
Exception
异常的分类:
编译时异常 :继承自Exception
的异常或者其子类,编译阶段就会报错,必须程序员处理的。否则代码编译就不能通过
运行时异常 : 继承自RuntimeException
的异常或者其子类,编译阶段是不会出错的,它是在运行时阶段可能出现,运行时异常可以处理也可以不处理,编译阶段是不会出错的,但是运行阶段可能出现,还是建议提前处理
常见的运行时异常==面试热点== 继承自RuntimeException
的异常或者其子类,编译阶段是不会出错的,它是在运行时阶段可能出现的错误,运行时异常编译阶段可以处理也可以不处理,代码编译都能通过
数组索引越界异常: ArrayIndexOutOfBoundsException
空指针异常 : NullPointerException
直接输出没有问题。但是调用空指针的变量的功能就会报错
类型转换异常:ClassCastException
迭代器遍历没有此元素异常:NoSuchElementException
数学操作异常:ArithmeticException
数字转换异常: NumberFormatException
编译时异常 编译时异常:继承自Exception
的异常或者其子类,没有继承RuntimeException
“编译时异常是编译阶段就会报错”,
必须程序员编译阶段就处理的。否则代码编译就报错
编译时异常的作用是什么:
是担心程序员的技术不行,在编译阶段就爆出一个错误, 目的在于提醒
提醒程序员这里很可能出错,请检查并注意不要出bug
第二章 异常的处理 异常的产生、处理的默认过程
默认会在出现异常的代码那里自动的创建一个异常对象:ArithmeticException
。
异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给JVM
虚拟机。
虚拟机接收到异常对象后,先在控制台直接输出异常栈信息数据。
直接从当前执行的异常点干掉当前程序。
后续代码没有机会执行了,因为程序已经死亡。
编译时异常处理机制 方法一
在出现编译时异常的地方层层把异常抛出去给调用者,调用者最终抛出给JVM虚拟机。
JVM虚拟机输出异常信息,直接干掉程序,这种方式与默认方式是一样的。
抛出异常格式:
1 2 3 4 5 6 7 方法 throws 异常1 , 异常2 , ..{ } 建议抛出异常的方式:代表可以抛出一切异常, 方法 throws Exception{ }
虽然可以解决代码编译时的错误,但是一旦运行时真的出现异常,程序还是会立即死亡
方法二
在出现异常的地方自己处理,谁出现谁处理
1 2 3 4 5 6 7 try { }catch (异常类型1 变量){ }catch (异常类型2 变量){ }...
第二种方式,可以处理异常,并且出现异常后代码也不会死亡。这种方案还是可以的。但是从理论上来说,这种方式不是最好的,上层调用者不能直接知道底层的执行情况
1 2 3 4 5 6 7 8 9 10 11 try { SimpleDateFormat sdf = new SimpleDateFormat ("yyyy/MM-dd HH:mm:ss" ); Date d = sdf.parse(time); System.out.println(d); InputStream is = new FileInputStream ("D:/meinv.png" ); } catch (FileNotFoundException e) { System.err.println("文件根本不存在!" ); } catch (ParseException e) { System.err.println("解析有问题,请检查代码!" ); }
方法三
在出现异常的地方吧异常一层一层的抛出给最外层调用者,最外层调用者集中捕获处理==规范做法==
这种方案最外层调用者可以知道底层执行的情况,同时程序在出现异常后也不会立即死亡,这是 理论上最好的方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public static void main (String[] args) { System.out.println("程序开始。。。。" ); try { parseDate("2013-03-23 10:19:23" ); System.out.println("功能成功执行!!" ); } catch (Exception e) { e.printStackTrace(); System.out.println("功能执行失败!!" ); } System.out.println("程序结束。。。。。" ); }public static void parseDate (String time) throws Exception { SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss" ); Date d = sdf.parse(time); System.out.println(d); InputStream is = new FileInputStream ("D:/meinv.png" ); }
运行时异常的处理机制 运行时异常在编译阶段是不会报错,在运行阶段才会出错。运行时异常在编译阶段不处理也不会报错,但是运行时如果出错了程序还是会死亡。所以运行时异常也建议要处理。
运行时异常是自动往外抛出的,不需要我们手工抛出。
运行时异常的处理规范:直接在最外层捕获处理即可,底层会自动抛出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public static void main (String[] args) { System.out.println("程序开始。。。。" ); try { chu(10 , 0 ); System.out.println("操作成功!" ); }catch (Exception e){ e.printStackTrace(); System.out.println("操作失败!" ); } System.out.println("程序结束。。。。" ); } public static void chu (int a , int b) { System.out.println( a / b ); }
finally关键字 用在捕获处理的异常格式中的,放在最后面
无论代码是出现异常还是正常执行,最终一定要执行这里的代码
1 2 3 4 5 6 7 try { }catch (Exception e){ e.printStackTrace(); }finally { }
finally
的作用: 可以在代码执行完毕以后进行资源的释放操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 try { is = new FileInputStream ("D:/cang.png" ); System.out.println(10 / 0 ); }catch (Exception e){ e.printStackTrace(); }finally { System.out.println("==finally被执行===" ); try { if (is!=null )is.close(); } catch (Exception e) { e.printStackTrace(); } }
异常的注意事项
运行时异常被抛出可以不处理。可以自动抛出,编译时异常必须处理.按照规范都应该处理!
重写方法申明抛出的异常,应该与父类被重写方法申明抛出的异常一样或者范围更小
方法默认都可以自动抛出运行时异常! throws RuntimeException
可以省略不写!!
当多异常处理时,捕获处理,前边的异常类不能是后边异常类的父类 。
在try/catch
后可以追加finally
代码块,其中的代码一定会被执行,通常用于资源回收操作。
第三章 自定义异常 自定义编译时异常
定义一个异常类继承Exception
重写构造器
在出现异常的地方用throw new
自定义对象抛出
编译时异常是编译阶段就报错,提醒更加强烈,一定需要处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class ItheimaAgeIllegalException extends Exception { public ItheimaAgeIllegalException () { } public ItheimaAgeIllegalException (String message) { super (message); } public ItheimaAgeIllegalException (String message, Throwable cause) { super (message, cause); } public ItheimaAgeIllegalException (Throwable cause) { super (cause); } public ItheimaAgeIllegalException (String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super (message, cause, enableSuppression, writableStackTrace); } }
自定义运行时异常
定义一个异常类继承RuntimeException
重写构造器
在出现异常的地方用throw new
自定义对象抛出
提醒不强烈,编译阶段不报错,运行时才可能出现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class ItheimaAgeIllegalRuntimeException extends RuntimeException { public ItheimaAgeIllegalRuntimeException () { } public ItheimaAgeIllegalRuntimeException (String message) { super (message); } public ItheimaAgeIllegalRuntimeException (String message, Throwable cause) { super (message, cause); } public ItheimaAgeIllegalRuntimeException (Throwable cause) { super (cause); } public ItheimaAgeIllegalRuntimeException (String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super (message, cause, enableSuppression, writableStackTrace); } }
异常的作用:
可以处理代码问题,防止程序出现异常后的死亡
提高了程序的健壮性和安全性
1 2 3 4 5 6 7 8 9 try { Scanner sc = new Scanner (System.in); System.out.println("请您输入您的年年龄:" ); int age = sc.nextInt(); System.out.println("您是:" +age); break ; }catch (Exception e){ System.err.println("您的年龄是瞎输入的!" ); }
第四章 多线程 进程与线程
进程 :是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
线程 :是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
进程的三个特征:
动态性 : 进程是运行中的程序,要动态的占用内存,CPU和网络等资源。
独立性 : 进程与进程之间是相互独立的,彼此有自己的独立内存区域。
并发性 : 假如CPU
是单核,同一个时刻其实内存中只有一个进程在被执行。
CPU
会分时轮询切换依次为每个进程服务,因为切换的速度非常快,给我们的感觉这些进程在同时执行,这就是并发性。
线程的作用
可以提高程序的效率,线程也支持并发性,可以有更多机会得到CPU。
多线程可以解决很多业务模型。
大型高并发技术的核心技术。
设计到多线程的开发可能都比较难理解。
线程常用方法 线程开启我们需要用到了java.lang.Thread
类,API中该类中定义了有关线程的一些方法,具体如下:
构造方法:
public Thread()
:分配一个新的线程对象。
public Thread(String name)
:分配一个指定名字的新的线程对象。
public Thread(Runnable target)
:分配一个带有指定目标新的线程对象。
public Thread(Runnable target,String name)
:分配一个带有指定目标新的线程对象并指定名字。
常用方法:
public void setName(String name)
:给当前线程取名字
public String getName()
:获取当前线程名称。
public void start()
:导致此线程开始执行; Java虚拟机调用此线程的run方法。
public void run()
:此线程要执行的任务在此处定义代码。
public static void sleep(long millis)
:使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
public static Thread currentThread()
:返回对当前正在执行的线程对象的引用。
翻阅API后得知创建线程的方式总共有两种,一种是继承Thread类方式,一种是实现Runnable接口方式,方式一我们上一天已经完成,接下来讲解方式二实现的方式。
线程的创建方式一-继承方式 Java使用java.lang.Thread
类代表线程 ,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。Java中通过继承Thread类来创建 并启动多线程 的步骤如下:
定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
创建Thread子类的实例,即创建了线程对象
调用线程对象的start()方法来启动该线程
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 ThreadDemo { public static void main (String[] args) { Thread t = new MyThread (); t.start(); for (int i = 0 ; i < 100 ; i++ ){ System.out.println("main线程输出:" +i); } } }class MyThread extends Thread { @Override public void run () { for (int i = 0 ; i < 100 ; i++ ){ System.out.println("子线程输出:" +i); } } }
线程的启动必须调用start()
方法,否则当成普通类处理
如果线程直接调用run()
方法,相当于变成了普通类的执行,此时只有主线程在执行他们
start()
方法底层其实是给CPU注册当前线程,并且触发run()
方法执行
建议线程先创建子线程,主线程的任务放在之后,否则主线程永远是先执行完
线程创建方式二-实现方式 采用java.lang.Runnable
也是非常常见的一种,我们只需要重写run方法即可。
步骤如下:
定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
调用线程对象的start()方法来启动线程。
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 ThreadDemo { public static void main (String[] args) { Runnable target = new MyRunnable (); Thread t = new Thread (target,"1号线程" ); t.start(); Thread t2 = new Thread (target); t2.start(); for (int i = 0 ; i < 10 ; i++ ){ System.out.println(Thread.currentThread().getName()+"==>" +i); } } }class MyRunnable implements Runnable { @Override public void run () { for (int i = 0 ; i < 10 ; i++ ){ System.out.println(Thread.currentThread().getName()+"==>" +i); } } }
匿名内部类方式 这种方式是实现方式的匿名内部类写法,代码更加简洁
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 NoNameInnerClassThread { public static void main (String[] args) { Runnable r = new Runnable (){ public void run () { for (int i = 0 ; i < 20 ; i++) { System.out.println("张宇:" +i); } } }; new Thread (r).start(); for (int i = 0 ; i < 20 ; i++) { System.out.println("费玉清:" +i); } } }
线程创建方式三-实现Callable接口
定义一个线程任务类实现Callable接口 , 申明线程执行的结果类型。
重写线程任务类的call方法,这个方法可以直接返回执行的结果。
创建一个Callable的线程任务对象。
把Callable的线程任务对象包装成一个未来任务对象。
把未来任务对象包装成线程对象。
调用线程的start()方法启动线程
这样做的优点是:
线程任务类只是实现了Callable接口,可以继续继承其他类,而且可以继续实现其他接口(避免了单继承的局限性)
同一个线程任务对象可以被包装成多个线程对象
适合多个多个线程去共享同一个资源(后面内容)
实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立。
线程池可以放入实现Runable或Callable线程任务对象。(后面了解)
能直接得到线程执行的结果!
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 ThreadDemo { public static void main (String[] args) { Callable call = new MyCallable (); FutureTask<String> task = new FutureTask <>(call); Thread t = new Thread (task); t.start(); for (int i = 1 ; i <= 10 ; i++ ){ System.out.println(Thread.currentThread().getName()+" => " + i); } try { String rs = task.get(); System.out.println(rs); } catch (Exception e) { e.printStackTrace(); } } }class MyCallable implements Callable <String>{ @Override public String call () throws Exception { int sum = 0 ; for (int i = 1 ; i <= 10 ; i++ ){ System.out.println(Thread.currentThread().getName()+" => " + i); sum+=i; } return Thread.currentThread().getName()+"执行的结果是:" +sum; } }
第五章 线程安全 线程安全问题:多个线程同时操作同一个共享资源的时候可能会出现线程安全问题
同步代码块
同步代码块 :synchronized
关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
格式:
1 2 3 synchronized (同步锁){ 需要同步操作的代码 }
同步锁 :
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
锁对象 可以是任意类型。
多个线程对象 要使用同一把锁。
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。
使用同步代码块解决代码:
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 Ticket implements Runnable { private int ticket = 100 ; Object lock = new Object (); @Override public void run () { while (true ){ synchronized (lock) { if (ticket>0 ){ try { Thread.sleep(50 ); } catch (InterruptedException e) { e.printStackTrace(); } String name = Thread.currentThread().getName(); System.out.println(name+"正在卖:" +ticket--); } } } } }
同步方法
同步方法 :使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
格式:
1 2 3 public synchronized void method () { 可能会产生线程安全问题的代码 }
同步锁是谁?
对于非static方法,同步锁就是this。
对于static方法,我们使用当前方法所在类的字节码对象(类名.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 public class Ticket implements Runnable { private int ticket = 100 ; @Override public void run () { while (true ){ sellTicket(); } } public synchronized void sellTicket () { if (ticket>0 ){ try { Thread.sleep(100 ); } catch (InterruptedException e) { e.printStackTrace(); } String name = Thread.currentThread().getName(); System.out.println(name+"正在卖:" +ticket--); } } }
Lock锁 java.util.concurrent.locks.Lock
机制提供了比synchronized 代码块和synchronized 方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
public void lock()
:加同步锁。
public void unlock()
:释放同步锁。
使用如下:
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 Ticket implements Runnable { private int ticket = 100 ; Lock lock = new ReentrantLock (); @Override public void run () { while (true ){ lock.lock(); if (ticket>0 ){ try { Thread.sleep(50 ); } catch (InterruptedException e) { e.printStackTrace(); } String name = Thread.currentThread().getName(); System.out.println(name+"正在卖:" +ticket--); } lock.unlock(); } } }
day8-线程状态、volatile关键字、原子性、并发包、死锁、线程池 第一章 线程状态 线程状态概述 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有几种状态呢?在API中java.lang.Thread.State
这个枚举中给出了六种线程状态:
这里先列出各个线程状态发生的条件,下面将会对每种状态进行详细解析
线程状态
导致状态发生条件
NEW(新建)
线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread只有线程对象,没有线程特征。
Runnable(可运行)
线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。调用了t.start()方法 :就绪(经典教法)
Blocked(锁阻塞)
当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
Waiting(无限等待)
一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
Timed Waiting(计时等待)
同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
Teminated(被终止)
因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。
睡眠方法 我们看到状态中有一个状态叫做计时等待,可以通过Thread类的方法来进行演示.
public static void sleep(long time)
让当前线程进入到睡眠状态,到毫秒后自动醒来继续执行
1 2 3 4 5 6 7 8 public class Test { public static void main (String[] args) { for (int i = 1 ;i<=5 ;i++){ Thread.sleep(1000 ); System.out.println(i) } } }
这时我们发现主线程执行到sleep方法会休眠1秒后再继续执行。
等待和唤醒 Object类的方法
public void wait()
: 让当前线程进入到等待状态 此方法必须锁对象调用.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Demo1_wait { public static void main (String[] args) throws InterruptedException { new Thread (() -> { try { System.out.println("begin wait ...." ); synchronized ("" ) { "" .wait(); } System.out.println("over" ); } catch (Exception e) { } }).start(); }
public void notify()
: 唤醒当前锁对象上等待状态的线程 此方法必须锁对象调用.
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 public class Demo2_notify { public static void main (String[] args) throws InterruptedException { new Thread (() -> { try { System.out.println("begin wait ...." ); synchronized ("" ) { "" .wait(); } System.out.println("over" ); } catch (Exception e) { } }).start(); Thread.sleep(3000 ); new Thread (() -> { try { synchronized ("" ) { System.out.println("唤醒" ); "" .notify(); } } catch (Exception e) { } }).start(); } }
第二章 线程通信
多个线程因为在同一个进程中,所以互相通信比较容易
线程通信一定是多个线程在操作同一个资源才需要进行通信
线程通信必须先保证线程安全,否则毫无意义
线程通信的核心方法:
public void wait()
: 让当前线程进入到等待状态 此方法必须锁对象调用.
public void notify()
: 唤醒当前锁对象上等待状态的某个线程 此方法必须锁对象调用
public void notifyAll()
: 唤醒当前锁对象上等待状态的全部线程 此方法必须锁对象调用
第三章 线程池 线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复的使用,省去了频繁创建和销毁线程对象的操作,无需反复创建线程而消耗过多资源。
合理利用线程池能够带来三个好处
降低资源消耗。 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
提高响应速度 不需要频繁的创建线程,如果有线程可以直接用,不会出现系统僵死!
提高线程的可管理性(线程池可以约束系统最多只能有多少个线程,不会因为线程过多而死机)
线程池的核心思想:线程复用,同一个线程可以被重复使用,来处理多个任务。
创建线程池 线程池在Java中的代表类:ExecutorService(接口)。
Java在Executors类下提供了一个静态方法得到一个线程池的对象:public static ExecutorService newFixedThreadPool(int nThreads)
:创建一个线程池返回。
ExecutorService提交线程任务对象执行的方法:Future<?> submit(Runnable task)
:提交一个Runnable的任务对象给线程池执行。
Future<?> submit(Callable task)
:提交一个Callable的任务对象给线程池执行。
pools.shutdown();
// 等待任务执行完毕以后才会关闭线程池
pools.shutdownNow();
// 立即关闭线程池的代码,无论任务是否执行完毕
线程池中的线程可以被复用,线程用完以后可以继续去执行其他任务。
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 import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class ThreadPoolsDemo02 { public static void main (String[] args) { ExecutorService pools = Executors.newFixedThreadPool(3 ); Runnable target = new MyRunnable (); pools.submit(target); pools.submit(target); pools.submit(target); pools.submit(target); pools.shutdown(); } }class MyRunnable implements Runnable { @Override public void run () { for (int i = 0 ; i < 5 ; i++ ){ System.out.println(Thread.currentThread().getName()+" => " +i); } } }
Callable
任务对象给线程池执行
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 ThreadPoolsDemo03 { public static void main (String[] args) { ExecutorService pools = Executors.newFixedThreadPool(3 ); Future<String> t1 = pools.submit(new MyCallable (10 )); Future<String> t2 = pools.submit(new MyCallable (20 )); Future<String> t3 = pools.submit(new MyCallable (30 )); Future<String> t4 = pools.submit(new MyCallable (40 )); try { String rs1 = t1.get(); String rs2 = t2.get(); String rs3 = t3.get(); String rs4 = t4.get(); System.out.println(rs1); System.out.println(rs2); System.out.println(rs3); System.out.println(rs4); }catch (Exception e){ e.printStackTrace(); } } }class MyCallable implements Callable <String>{ private int n; public MyCallable (int n) { this .n = n; } @Override public String call () throws Exception { int sum = 0 ; for (int i = 1 ; i <= n ; i++){ System.out.println(Thread.currentThread().getName()+" => " +i); sum += i ; } return Thread.currentThread().getName()+"计算1-" +n+"的和:" +sum; } }
第四章 死锁 死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
java 死锁产生的四个必要条件:
互斥使用 ,即当资源被一个线程使用(占有)时,别的线程不能使用
不可抢占 ,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放
求和保持 ,即当资源请求者在请求其他的资源的同时保持对原有资源的占有
循环等待 ,即存在一个等待循环队列:p1要p2的资源,p2要p1的资源。这样就形成了一个等待环路
当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失
第五章 volatile关键字 问题 :线程修改了某个成员变量的值,但是在主线程中读取到的还是之前的值修改后的值无法读取到。
原因 :按照JMM模型,所有的成员变量和静态变量都存在于主内存中,主内存中的变量可以被多个线程共享。每个线程都存在一个专属于自己的工作内存,工作内存一开始存储的是成员变量的副本。所以线程很多时候都是直接访问自己工作内存中的该变量,其他线程对主内存变量值的修改将不可见
希望所有线程对于主内存的成员变量修改,其他线程是可见的。
加锁 :可以实现其他线程对变量修改的可见性 某一个线程进入synchronized
代码块前后,执行过程入如下:
线程获得锁
清空工作内存
从主内存拷贝共享变量最新的值到工作内存成为副本
可以给成员变量加上一个volatile
关键字,立即就实现了成员变量多线程修改的可见性
volatile
与synchronized
的区别。
volatile
只能修饰实例变量和静态变量,而synchronized
可以修饰方法,以及代码块。
volatile
保证数据的可见性,但是不保证原子性(多线程进行写操作,不保证线程安全);而synchronized
是一种排他(互斥)的机制,
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 VolatileDemo01 { public static void main (String[] args) { VolatileThread t = new VolatileThread (); t.start(); while (true ){ if (t.isFlag()){ System.out.println("执行了循环一次~~~~~~~" ); } } } }class VolatileThread extends Thread { private volatile boolean flag = false ; public boolean isFlag () { return flag; } @Override public void run () { try { Thread.sleep(1000 ); } catch (Exception e) { e.printStackTrace(); } this .flag = true ; System.out.println("线程修改了flag=" + flag); } }
第六章 原子性 原子性 是指在一次操作或者多次操作中,所有的操作全部都得到了执行并且不会受到任何因素的干扰。最终结果要保证线程安全。
在多线程环境下,volatile
关键字可以保证共享数据的可见性,但是并不能保证对数据操作的原子性(在多线程环境下volatile修饰的变量也是线程不安全的)。volatile
的使用场景
解决方法一-加锁 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 VolatileAtomicThread implements Runnable { private volatile int count = 0 ; @Override public void run () { for (int x = 0 ; x < 100 ; x++) { synchronized (this ){ count++ ; System.out.println(Thread.currentThread().getName() + "count =========>>>> " + count); } } } }class VolatileAtomicThreadDemo { public static void main (String[] args) { Runnable target = new VolatileAtomicThread () ; for (int x = 0 ; x < 100 ; x++) { new Thread (target).start(); } } }
这种方法虽然安全性得到了保证,但是性能不好
解决方法二-基于CAS方式的原子类 Java已经提供了一些本身即可实现原子性(线程安全)的类。
概述:java从JDK1.5开始提供了java.util.concurrent.atomic包(简称Atomic包),这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式。
操作整型的原子类
public AtomicInteger()
: 初始化一个默认值为0的原子型Integer
public AtomicInteger(int initialValue)
: 初始化一个指定值的原子型Integer
int get()
: 获取值
int getAndIncrement()
: 以原子方式将当前值加1,注意,这里返回的是自增前的值。
int incrementAndGet()
: 以原子方式将当前值加1,注意,这里返回的是自增后的值。
int addAndGet(int data)
: 以原子方式将输入的数值与实例中的值(AtomicInteger
里的value)相加,并返回结果。
int getAndSet(int value)
: 以原子方式设置为newValue
的值,并返回旧值。
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 VolatileAtomicThread implements Runnable { private AtomicInteger atomicInteger = new AtomicInteger (); @Override public void run () { for (int x = 0 ; x < 100 ; x++) { int count = atomicInteger.incrementAndGet(); System.out.println("count =========>>>> " + count); } } }class VolatileAtomicThreadDemo { public static void main (String[] args) { Runnable target = new VolatileAtomicThread () ; for (int x = 0 ; x < 100 ; x++) { new Thread (target).start(); } } }
CAS与Synchronized总结
Synchronized
是从悲观的角度出发:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程 )。因此Synchronized
我们也将其称之为悲观锁 。jdk中的ReentrantLock
也是一种悲观锁。性能较差
CAS
是从乐观的角度出发:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。CAS这种机制我们也可以将其称之为乐观锁 。综合性能较好
第七章 并发包 并发包的来历: 在实际开发中如果不需要考虑线程安全问题,大家不需要做线程安全,因为如果做了反而性能不好! 但是开发中有很多业务是需要考虑线程安全问题的,此时就必须考虑了。否则业务出现问题。 Java为很多业务场景提供了性能优异,且线程安全的并发包,程序员可以选择使用!
ConcurrentHashMap Map
集合中的经典集合:HashMap
它是线程不安全的,性能好,如果在要求线程安全的业务情况下就不能用这个集合做Map集合,否则业务会崩溃
为了保证线程安全,可以使用Hashtable
。注意:线程中加入了计时,Hashtable
是线程安全的Map
集合,但是性能较差!(已经被淘汰了,虽然安全,但是性能差)
为了保证线程安全,再看ConcurrentHashMap
(不止线程安全,而且效率高,性能好,最新最好用的线程安全的Map集合)ConcurrentHashMap
保证了线程安全,综合性能较好!
HashMap
是线程不安全的。
Hashtable
线程安全基于synchronized
,综合性能差,被淘汰了。
ConcurrentHashMap
:线程安全的,分段式锁,综合性能最好,线程安全开发中推荐使用
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 public class ConcurrentHashMapDemo { public static Map<String,String> map = new Hashtable <>(); public static void main (String[] args) throws InterruptedException { new AddMapDataThread ().start(); new AddMapDataThread ().start(); Thread.sleep(1000 * 4 ); System.out.println("Map大小:" + map.size()); } }class AddMapDataThread extends Thread { @Override public void run () { for (int i = 0 ; i < 1000000 ; i++ ){ ConcurrentHashMapDemo.map.put(Thread.currentThread().getName()+"键:" +i , "值" +i); } } }
CountDownLatch CountDownLatch
允许一个或多个线程等待其他线程完成操作,再执行自己。
例如:
线程1要执行打印:A和C,线程2要执行打印:B,但线程1在打印A后,要线程2打印B之后才能打印C,所以:线程1在打印A后,必须等待线程2打印完B之后才能继续执行
需求:
提供A线程,打印 A , C 提供B线程,打印 B
构造器:
public CountDownLatch(int count)
// 初始化唤醒需要的down几步。
方法:public void await() throws InterruptedException
// 让当前线程等待,必须down完初始化的数字才可以被唤醒,否则进入无限等待public void countDown()
// 计数器进行减1 (down 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 31 32 33 34 35 36 37 public class CountDownLatchDemo { public static void main (String[] args) { CountDownLatch down = new CountDownLatch (1 ); new ThreadA (down).start(); new ThreadB (down).start(); } }class ThreadA extends Thread { private CountDownLatch down; public ThreadA (CountDownLatch down) { this .down = down; } @Override public void run () { System.out.println("A" ); try { down.await(); } catch (Exception e) { e.printStackTrace(); } System.out.println("C" ); } }class ThreadB extends Thread { private CountDownLatch down; public ThreadB (CountDownLatch down) { this .down = down; } @Override public void run () { System.out.println("B" ); down.countDown(); } }
CyclicBarrier CyclicBarrier
作用:某个线程任务必须等待其他线程执行完毕以后才能最终触发自己执行。
例如:公司召集5名员工开会,等5名员工都到了,会议开始。我们创建5个员工线程,1个开会任务,几乎同时启动。使用CyclicBarrier
保证5名员工线程全部执行后,再执行开会线程。
构造器:public CyclicBarrier(int parties, Runnable barrierAction)
// 用于在线程到达屏障5时,优先执行barrierAction
,方便处理更复杂的业务场景 方法: public int await()
// 每个线程调用await方法告诉CyclicBarrier
我已经到达了屏障,然后当前线程被阻塞
可以实现多线程中,某个任务在等待其他线程执行完毕以后触发。
循环屏障可以实现达到一组屏障就触发一个任务执行!
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 CyclicBarrierDemo { public static void main (String[] args) { CyclicBarrier cb = new CyclicBarrier (5 , new MeetingRunnable ()); new PeopleThread (cb).start(); new PeopleThread (cb).start(); new PeopleThread (cb).start(); new PeopleThread (cb).start(); new PeopleThread (cb).start(); new PeopleThread (cb).start(); new PeopleThread (cb).start(); new PeopleThread (cb).start(); new PeopleThread (cb).start(); new PeopleThread (cb).start(); } }class MeetingRunnable implements Runnable { @Override public void run () { System.out.println("人员到齐了开始由" +Thread.currentThread().getName()+"主持会议!" ); } }class PeopleThread extends Thread { private CyclicBarrier cb ; public PeopleThread (CyclicBarrier cb) { this .cb = cb; } @Override public void run () { try { Thread.sleep(1000 ); System.out.println("员工:" +Thread.currentThread().getName()+"进入会议室" ); cb.await(); } catch (Exception e) { e.printStackTrace(); } } }
Semaphore
Semaphore
(发信号)的主要作用是控制线程的并发数量。
synchronized
可以起到”锁”的作用,但某个时间段内,只能有一个线程允许执行。
Semaphore
可以设置同时允许几个线程执行。
Semaphore
字面意思是信号量的意思,它的作用是控制访问特定资源的线程数目。
Semaphore
的构造器:
public Semaphore(int permits)
: permits 表示许可线程的数量
public Semaphore(int permits, boolean fair)
:fair 表示公平性,如果这个设为 true 的话,下次执行的线程会是等待最久的线程
Semaphore
的方法:
public void acquire() throws InterruptedException
表示获取许可
public void release() release()
表示释放许可
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 SemaphoreDemo { public static void main (String[] args) { Service service = new Service (); for (int i = 1 ; i <= 5 ; i++ ){ new MyThread (service,"线程:" +i).start(); } } }class Service { private Semaphore semaphore = new Semaphore (2 ); public void showMethod () { try { semaphore.acquire(); long startTimer = System.currentTimeMillis(); System.out.println("进入时间:" +startTimer); System.out.println(Thread.currentThread().getName()+"进入资源执行" ); Thread.sleep(1000 ); } catch (Exception e) { e.printStackTrace(); } long endTimer = System.currentTimeMillis(); System.out.println("结束时间:" +endTimer); semaphore.release(); } }class MyThread extends Thread { private Service service; public MyThread (Service service , String name) { super (name); this .service = service; } @Override public void run () { service.showMethod(); } }
Exchanger Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。这两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。
Exchanger构造方法:public Exchanger()
Exchanger重要方法:public V exchange(V x)
分析: (1)需要2个线程 (2)需要一个交换对象负责交换两个线程执行的结果。
Exchanger可以实现线程间的数据交换。
一个线程如果等不到对方的数据交换就会一直等待。
我们也可以控制一个线程等待的时间。
必须双方都进行交换才可以正常进行数据的交换。
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 ExchangerDemo { public static void main (String[] args) { Exchanger<String> exchanger = new Exchanger <>(); new ThreadA (exchanger).start(); new ThreadB (exchanger).start(); } }class ThreadA extends Thread { private Exchanger<String> exchanger; public ThreadA (Exchanger<String> exchanger) { this .exchanger = exchanger; } @Override public void run () { try { System.out.println("线程A,做好了礼物A,等待线程B送来的礼物B....." ); System.out.println("线程A收到线程B的礼物:" +exchanger.exchange("礼物A" , 5 , TimeUnit.SECONDS)); } catch (Exception e) { System.out.println("线程A等待了5s,没有收到礼物,最终就执行结束了!" ); } } }class ThreadB extends Thread { private Exchanger<String> exchanger; public ThreadB (Exchanger<String> exchanger) { this .exchanger = exchanger; } @Override public void run () { try { System.out.println("线程B,做好了礼物B,等待线程A送来的礼物A....." ); System.out.println("线程B收到线程A的礼物:" +exchanger.exchange("礼物B" )); } catch (Exception e) { e.printStackTrace(); } } }
day9-方法引用、Lambda表达式、Stream流 第一章 方法引用 方法引用概述 方法引用是为了进一步简化Lambda表达式的写法。
方法引用的格式:类型或者对象::引用的方法。
方法引用有四种形式:
静态方法的引用
实例方法的引用
特定类型方法的引用
构造器引用
1 2 3 4 5 6 7 8 ist<String> lists = new ArrayList <>(); lists.add("java1" ); lists.add("java2" ); lists.add("java3" ); lists.forEach( s -> System.out.println(s)); lists.forEach(System.out::println);
静态方法引用 引用格式:类名::静态方法
简化步骤:定义一个静态方法,把需要简化的代码放到一个静态方法中去。
静态方法引用的注意事项:被引用的方法的参数列表要和函数式接口中的抽象方法的参数列表一致
Student
1 2 3 4 5 6 7 8 9 10 public class Student { private String name ; private int age ; private char sex ; public static int compareByAge (Student o1 , Student o2) { return o1.getAge() - o2.getAge(); } }
排序
1 2 3 4 5 6 7 8 9 List<Student> lists = new ArrayList <>();Student s1 = new Student ("李铭" ,18 ,'男' );Student s2 = new Student ("冯龙" ,23 ,'男' );Student s3 = new Student ("王乐乐" ,21 ,'男' ); Collections.addAll(lists , s1 , s2 , s3); Collections.sort(lists, ( o1, o2) -> Student.compareByAge(o1 , o2)); Collections.sort(lists, Student::compareByAge);
实例方法引用 格式: 对象::实例方法
简化步骤:定义一个实例方法,把需要的代码放到实例方法中去。
实例方法引用的注意事项:被引用的方法的参数列表要和函数式接口中的抽象方法的参数列表一致
1 2 3 4 5 6 7 8 9 10 List<String> lists = new ArrayList <>(); lists.add("java1" ); lists.add("java2" ); lists.add("java3" ); lists.forEach(s -> System.out.println(s)); lists.forEach(System.out::println);
特定类型方法的引用 特定类型:String ,任何类型
格式:特定类型::方法
注意:如果第一个参数列表中的形参中的第一个参数作为了后面的方法的调用者,并且其余参数作为后面方法的形参,那么就可以用特定类型方法引用了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 String[] strs = new String []{"James" , "AA" , "John" , "Patricia" ,"Dlei" , "Robert" ,"Boom" , "Cao" ,"black" , "Michael" , "Linda" ,"cao" ,"after" ,"sBBB" }; Arrays.sort(strs, new Comparator <String>() { @Override public int compare (String s1, String s2) { return s1.compareToIgnoreCase(s2); } }); Arrays.sort(strs, (String s1, String s2) -> { return s1.compareToIgnoreCase(s2); }); Arrays.sort(strs, ( s1, s2 ) -> s1.compareToIgnoreCase(s2)); Arrays.sort(strs, String::compareToIgnoreCase); System.out.println(Arrays.toString(strs));
构造器引用 格式是:类名::new
注意点:前后参数一致的情况下,又在创建对象就可以使用构造器引用s -> new Student(s) => Student::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 List<String> lists = new ArrayList <>(); lists.add("java1" ); lists.add("java2" ); lists.add("java3" ); Object[] objs = lists.toArray(); System.out.println("Object类型的数组:" + Arrays.toString(objs)); String[] strs = lists.toArray(new IntFunction <String[]>() { @Override public String[] apply(int value) { return new String [value]; } }); String[] strs1 = lists.toArray(s -> new String [s] ); String[] strs2 = lists.toArray(String[]::new ); System.out.println("String类型的数组:" + Arrays.toString(strs2));
第二章 Lambda表达式 Lambda表达式是JDK1.8开始之后的新技术,是一种代码的新语法,作用是为了简化匿名内部类的代码写法
Lambda表达式的格式
1 2 3 (匿名内部类被重写方法的形参列表) -> { }
Lambda表达式的使用前提:
Lambda表达式并不能简化所有匿名内部类的写法。
Lambda表达式只能简化接口中只有一个抽象方法的匿名内部类形式。
Lambda表达式只能简化函数式接口的匿名内部类写法 :
Lambda表达式简化Runnable接口匿名内部类 @FunctionalInterface
函数式接口注解:一旦某个接口加上了这个注解,这个接口只能有且仅有一个抽象方法。 这个接口就可以被Lambda表达式简化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Thread t = new Thread (new Runnable () { @Override public void run () { System.out.println(Thread.currentThread().getName()+":执行~~~" ); } }); t.start();Thread t1 = new Thread (() -> { System.out.println(Thread.currentThread().getName()+":执行~~~" ); }); t1.start();new Thread (() -> { System.out.println(Thread.currentThread().getName()+":执行~~~" ); }).start();new Thread (() -> System.out.println(Thread.currentThread().getName()+":执行~~~" )).start();
Lambda表达式简化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 List<Student> lists = new ArrayList <>();Student s1 = new Student ("李铭" ,18 ,'男' );Student s2 = new Student ("冯龙" ,23 ,'男' );Student s3 = new Student ("王乐乐" ,21 ,'男' ); Collections.addAll(lists , s1 , s2 , s3); Collections.sort(lists, new Comparator <Student>() { @Override public int compare (Student s1, Student s2) { return s1.getAge() - s2.getAge(); } }); Collections.sort(lists ,(Student t1, Student t2) -> { return t1.getAge() - t2.getAge(); }); Collections.sort(lists ,(Student t1, Student t2) -> t1.getAge() - t2.getAge()); Collections.sort(lists ,( t1, t2) -> t1.getAge() - t2.getAge()); System.out.println(lists);
Lambda表达式的省略写法 Lambda表达式的省略写法(进一步在Lambda表达式的基础上继续简化)
如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写,同时要省略分号
如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写。此时,如果这行代码是return语句,必须省略return不写,同时也必须省略”;”不写
参数类型可以省略不写
如果只有一个参数,参数类型可以省略,同时()也可以省略
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 List<String> names = new ArrayList <>(); names.add("胡伟光" ); names.add("甘挺" ); names.add("洪磊" ); names.forEach(new Consumer <String>() { @Override public void accept (String s) { System.out.println(s); } }); names.forEach((String s) -> { System.out.println(s); }); names.forEach((s) -> { System.out.println(s); }); names.forEach(s -> { System.out.println(s); }); names.forEach(s -> System.out.println(s) ); names.forEach(System.out::println);
第三章 Stream流 Stream流概述 在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream流概念 ,用于解决已有集合/数组类库有的弊端。
Stream流能解决什么问题:
可以解决已有集合类库或者数组API的弊端。
Stream认为集合和数组操作的API很不好用,所以采用了Stream流简化集合和数组的操作
1 2 3 4 5 6 7 8 9 List<String> list = new ArrayList <>(); list.add("张无忌" ); list.add("周芷若" ); list.add("赵敏" ); list.add("张强" ); list.add("张三丰" ); list.stream().filter(s -> s.startsWith("张" )).filter( s -> s.length()== 3 ) .forEach(System.out::println);
Stream流的获取 Stream流式思想的核心:
是先得到集合或者数组的Stream流(就是一根传送带)
然后就用这个Stream流操作集合或者数组的元素
然后用Stream流简化替代集合操作的API
1 default Stream<E> stream () ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Collection<String> c = new ArrayList <>(); Stream<String> ss = c.stream(); Map<String, Integer> map = new HashMap <>(); Stream<String> keyss = map.keySet().stream(); Stream<Integer> valuess = map.values().stream(); Stream<Map.Entry<String,Integer>> keyAndValues = map.entrySet().stream(); String[] arrs = new String []{"Java" , "JavaEE" ,"Spring Boot" }; Stream<String> arrsSS1 = Arrays.stream(arrs); Stream<String> arrsSS2 = Stream.of(arrs);
Stream流的常用API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 List<String> list = new ArrayList <>(); list.add("张无忌" ); list.add("周芷若" ); list.add("赵敏" ); list.add("张强" ); list.add("张三丰" ); list.add("张三丰" ); list.stream().filter( s -> s.length() == 3 ).filter( s -> s.startsWith("张" )) .forEach( System.out::println);long count = list.stream().filter( s -> s.length() == 3 ) .filter( s -> s.startsWith("张" )).count(); System.out.println(count); list.stream().filter(s -> s.length() == 3 ).limit(2 ) .forEach(System.out::println); list.stream().filter(s -> s.length() == 3 ).skip(2 ) .forEach(System.out::println); list.stream().map(Student::new ).forEach(System.out::println);
合并流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 List<String> list = new ArrayList <>(); list.add("张无忌" ); list.add("周芷若" ); list.add("赵敏" ); list.add("张强" ); list.add("张三丰" ); list.add("张三丰" ); Stream<Integer> s1 = Stream.of(10 , 20 ,30 ,40 ); Stream<String> s2 = list.stream(); Stream<Object> s3 = Stream.concat(s1,s2); s3.forEach(System.out::println);
Stream流的综合应用 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 List<String> one = new ArrayList <>(); one.add("迪丽热巴" ); one.add("宋远桥" ); one.add("苏星河" ); one.add("老子" ); one.add("庄子" ); one.add("孙子" ); one.add("洪七公" ); List<String> two = new ArrayList <>(); two.add("古力娜扎" ); two.add("张无忌" ); two.add("张三丰" ); two.add("赵丽颖" ); two.add("张二狗" ); two.add("张天爱" ); two.add("张三" ); Stream<String> oneStream = one.stream().filter(s -> s.length() == 3 ).limit(3 ); Stream<String> twoStream = two.stream().filter(s -> s.startsWith("张" )).skip(2 ); Stream<String> allStream = Stream.concat(oneStream , twoStream); allStream.map(Student::new ).forEach(System.out::println);
Stream流的终结与非终结方法 一旦Stream调用了终结方法,流的操作就全部终结了,不能继续使用,只能创建新的Stream操作。
终结方法: foreach
, count
非终结方法:每次调用完成以后返回一个新的流对象,可以继续使用,支持链式编程!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 List<String> list = new ArrayList <>(); list.add("张无忌" ); list.add("周芷若" ); list.add("赵敏" ); list.add("张强" ); list.add("张三丰" ); list.add("张三丰" ); list.stream().filter(s -> s.startsWith("张" )) .filter(s -> s.length() == 3 ).forEach(System.out::println);long count = list.stream().filter(s -> s.startsWith("张" )) .filter(s -> s.length() == 3 ).count(); System.out.println(count);
收集Stream流 收集Stream流:把Stream流的数据转回成集合。
Stream的作用是:把集合转换成一根传送带,借用Stream流的强大功能进行的操作。但是实际开发中数据最终的形式还是应该是集合,最终Stream流操作完毕以后还是要转换成集合。这就是收集Stream流。
收集Stream流的含义:就是把Stream流的数据转回到集合中去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 List<String> list = new ArrayList <>(); list.add("张无忌" ); list.add("周芷若" ); list.add("赵敏" ); list.add("张强" ); list.add("张三丰" ); list.add("张三丰" ); Stream<String> zhangLists = list.stream().filter(s -> s.startsWith("张" )); Set<String> sets = zhangLists.collect(Collectors.toSet()); System.out.println(sets); Stream<String> zhangLists1 = list.stream().filter(s -> s.startsWith("张" )); List<String> lists= zhangLists1.collect(Collectors.toList()); System.out.println(lists); Stream<String> zhangLists2 = list.stream().filter(s -> s.startsWith("张" )); Object[] arrs = zhangLists2.toArray();
第四章 File类 File类的概述 File类:代表操作系统的文件对象
File类:是用来操作操作系统的文件对象的,删除文件,获取文件信息,创建文件(文件夹)…
广义来说操作系统认为文件包含(文件和文件夹)
File类的创建文件对象的API:
包:java.io.File
构造器:
public File(String pathname)
:根据路径获取文件对象
public File(String parent, String child)
:根据父路径和文件名称获取文件对象!
File类创建文件对象的格式:
File f = new File("绝对路径/相对路径");
绝对路径 :从磁盘的的盘符一路走到目的位置的路径。
绝对路径依赖具体的环境,一旦脱离环境,代码可能出错!!
一般是定位某个操作系统中的某个文件对象。
相对路径 :不带盘符的。(重点)
默认是直接相对到工程目录下寻找文件的。
相对路径只能用于寻找工程下的文件。
能用相对路径就应该尽量使用,可以跨平台!
File f = new File("文件对象/文件夹对象");
广义来说:文件是包含文件和文件夹的。
创建文件对象可以用绝对路径也可以用相对路径。
相对路径只能用于寻找工程下的文件。
文件对象可以表示文件也可以表示文件夹!
1 2 3 4 5 6 7 8 9 10 File f1 = new File ("D:\\itcast\\图片资源\\beautiful.jpg" ); System.out.println(f1.length()); File f2 = new File ("Day09Demo/src/dlei01.txt" ); System.out.println(f2.length());File f3 = new File ("D:\\itcast\\图片资源" ); System.out.println(f3.exists());
File类的获取功能的方法
public String getAbsolutePath()
:返回此File的绝对路径名字符串。
public String getPath()
: 获取创建文件对象的时候用的路径
public String getName()
: 返回由此File表示的文件或目录的名称。
public long length()
: 返回由此File表示的文件的长度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 File f1 = new File ("D:/itcast/图片资源/meinv.jpg" ); System.out.println(f1.getAbsolutePath()); System.out.println(f1.getPath()); System.out.println(f1.getName()); System.out.println(f1.length()); System.out.println("------------------------" );File f2 = new File ("Day09Demo/src/dlei01.txt" ); System.out.println(f2.getAbsolutePath()); System.out.println(f2.getPath()); System.out.println(f2.getName()); System.out.println(f2.length());
File类的判断功能的方法
public boolean exists()
:此File表示的文件或目录是否实际存在。
public boolean isDirectory()
:此File表示的是否为目录。
public boolean isFile()
:此File表示的是否为文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 File f1 = new File ("D:\\itcast\\图片资源\\meinv.jpg" ); System.out.println(f1.exists()); System.out.println(f1.isFile()); System.out.println(f1.isDirectory()); File f2 = new File ("D:\\itcast\\图片资源" ); System.out.println(f2.exists()); System.out.println(f2.isFile()); System.out.println(f2.isDirectory());
File类的创建和删除方法
public boolean createNewFile()
:当且仅当具有该名称的文件尚不存在时, 创建一个新的空文件。 (几乎不用的,因为以后文件都是自动创建的!)
public boolean delete()
:删除由此File表示的文件或目录。 (只能删除空目录)
public boolean mkdir()
:创建由此File表示的目录。(只能创建一级目录)
public boolean mkdirs()
:可以创建多级目录(建议使用的)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 File f = new File ("Day09Demo/src/dlei02.txt" ); System.out.println(f.createNewFile()); System.out.println(f.delete());File f1 = new File ("D:/itcast/aaaaa" ); System.out.println(f1.delete());File f2 = new File ("D:/itcast/bbbb" ); System.out.println(f2.mkdir());File f3 = new File ("D:/itcast/e/a/d/ds/fas/fas/fas/fas/fas/fas" ); System.out.println(f3.mkdirs());
File目录的遍历
public String[] list()
获取当前目录下所有的”一级文件名称”到一个字符串数组中去返回。
public File[] listFiles()
==常用== 获取当前目录下所有的”一级文件对象”到一个文件对象数组中去返回(重点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 File dir = new File ("day09/src/com/itheima" ); String[] names = dir.list();for (String name : names) { System.out.println(name); } File[] files = dir.listFiles();for (File file : files) { System.out.println(file.getAbsolutePath()); }File f1 = new File ("C:\\Users\\Administrator\\Documents\\codes\\notes\\java-notes\\java补充知识点\\codes\\seniorJava\\day09\\src\\com\\itheima\\_20File目录的遍历\\FileDemo.java" );long time = f1.lastModified(); SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss" ); System.out.println(sdf.format(time));
第五章 递归 递归的概述 方法在方法中又调用了自己
直接递归:自己的方法调用自己
间接递归:自己的方法调用别的方法,别的方法又调用自己
递归是自己调用自己。
递归如果控制的不恰当,会形成递归的死循环,从而导致栈内存溢出错误!!
递归应该防止进入递归的死循环!
一个简单的例子,计算 $$ f(x) = f(x-1) + 1 $$
1 2 3 4 5 6 7 8 9 10 11 12 13 public class RecursionDemo02 { public static void main (String[] args) { System.out.println(f(10 )); } public static int f (int x) { if (x == 1 ) { return 1 ; }else { return f(x - 1 ) + 1 ; } } }
递归的核心 递归算法分为三个要素:
递归公式 $$ f(x)=f(x)+1 $$
递归终结点 $$ f(1)=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 31 32 33 34 35 public static void searchFiles (File dir , String fileName) { if (dir.exists() && dir.isDirectory()){ File[] files = dir.listFiles(); if (files!=null && files.length > 0 ){ for (File f : files) { if (f.isFile()){ if (f.getName().contains(fileName)){ System.out.println(f.getAbsolutePath()); try { Runtime r = Runtime.getRuntime(); r.exec(f.getAbsolutePath()); } catch (IOException e) { e.printStackTrace(); } } }else { searchFiles(f ,fileName); } } } } }
第六章 字节流 字符集 字符集:各个国家为自己国家的字符取的一套编号规则。计算机的底层是不能直接存储字符的。计算机的底层只能存储二进制。010101二进制就是可以转成10进制的。10进制就是整数编号。101 = 12^0 + 0 2^1 + 1*2^2 = 5
中国用的编码:GBK编码
美国用的编码:ACSII编码
IO流读写数据 IO输入输出流:输入/输出流。
引入: File类只能操作文件对象本身,不能读写文件对象的内容。 读写数据内容,应该使用IO流。
IO流是一个水流模型:IO理解成水管,把数据理解成水流。
IO流的分类
按照流的方向分为:输入流,输出流。
输出流:以内存为基准,把内存中的数据写出到磁盘文件或者网络介质中去的流称为输出流。 输出流的作用:写数据到文件,或者写数据发送给别人。
输入流:以内存为基准,把磁盘文件中的数据或者网络中的数据读入到内存中去的流称为输入流。 输入流的作用:读取数据到内存。
按照流的内容分为: 字节流,字符流。
字节流:流中的数据的最小单位是一个一个的字节,这个流就是字节流。
字符流:流中的数据的最小单位是一个一个的字符,这个流就是字符流。(针对于文本内容)
所以流大体分为四大类:
字节输入流:以内存为基准,把磁盘文件中的数据或者网络中的数据以一个一个的字节的形式读入到内存中去的流称为字节输入流。
字节输出流:以内存为基准,把内存中的数据以一个一个的字节写出到磁盘文件或者网络介质中去的流称为字节输出流。
字符输入流:以内存为基准,把磁盘文件中的数据或者网络中的数据以一个一个的字符的形式读入到内存中去的流称为字符输入流。
字符输出流:以内存为基准,把内存中的数据以一个一个的字符写出到磁盘文件或者网络介质中去的流称为字符输出流。
IO流是读写传输数据的,IO流有很多种,每种流有自己的功能特点。
字节流的使用 IO流的体系
字节流
字节流
字符流
字符流
字节输入流
字节输出流
字符输入流
字符输出流
InputStream
OutputStream
Reader
Writer(抽象类)
FileInputStream
FileOutputStream
FileReader
FileWriter(子类实现类)
输入流 FileInputStream文件字节输入流
作用:以内存为基准,把磁盘文件中的数据按照字节的形式读入到内存中的流,简单的来说,就是按照字节读取文件数据到内存
构造器
public FileInputStream(File path)
:创建一个字节输入流管道与源文件对象接通
public FileInputStream(String pathName)
:创建一个字节输入流管道与文件路径对接
方法
public int read()
每次读取一个直接返回,读取完毕会返回-1
public int read(byte[] buffer)
从字节输入流中读取字节到字节数组中去,返回读取的字节数量,没有字节可读返回-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 31 32 33 File file = new File ("Day09Demo/src/dlei01.txt" );InputStream is = new FileInputStream (file);int ch = 0 ;while ((ch = is.read())!= -1 ){ System.out.print((char ) ch); }byte [] buffer = new byte [3 ];int len ; while ((len = is.read(buffer)) != -1 ){ String rs = new String (buffer , 0 , len); System.out.print(rs); }
解决中文乱码
定义一个字节数组与文件的大小刚刚一样大,然后一桶水读取全部字节数据再输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 File f = new File ("C:\\Users\\Administrator\\Documents\\codes\\notes\\java-notes\\java补充知识点\\codes\\seniorJava\\day09\\src\\com\\itheima\\_25字节流的使用\\FileInputStreamDemo03.java" );InputStream is = new FileInputStream (f);byte [] buffer = is.readAllBytes();String rs = new String (buffer); System.out.println(rs);
输出流 FileOutputStream
文件字节输出流
作用:以内存为基准,把内存中的数据,按照字节的形式写出到磁盘文件中去。简单来说,把内存数据按照字节写出到磁盘文件中去。
构造器:
public FileOutputStream(File file)
:创建一个字节输出流管道通向目标文件对象。
public FileOutputStream(String file)
:创建一个字节输出流管道通向目标文件路径。
public FileOutputStream(File file , boolean append)
:创建一个追加数据的字节输出流管道通向目标文件对象。
public FileOutputStream(String file , boolean append)
:创建一个追加数据的字节输出流管道通向目标文件路径。
方法:
public void write(int a)
:写一个字节出去 。
public void write(byte[] buffer)
:写一个字节数组出去。
public void write(byte[] buffer , int pos , int len)
:写一个字节数组的一部分出去。
参数一,字节数组;参数二:起始字节索引位置,参数三:写多少个字节数出去。
字节输出流只能写字节出去
字节输出流默认是覆盖数据管道
换行用: os.write("\r\n".getBytes());
关闭和刷新:刷新流可以继续使用,关闭包含刷新数据但是流就不能使用了!
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 InputStream is = null ;OutputStream os = null ;try { is = new FileInputStream ("D:\\itcast\\图片资源\\meinv.jpg" ); os = new FileOutputStream ("D:\\itcast\\meimei.jpg" ); byte [] buffer = new byte [1024 ]; int len = 0 ; while ((len = is.read(buffer)) != -1 ){ os.write(buffer, 0 , len); } System.out.println("复制完成!" ); }catch (Exception e){ e.printStackTrace(); } finally { try { if (os!=null )os.close(); if (is!=null )is.close(); }catch (Exception e){ e.printStackTrace(); } }
JDK1.7 开始之后释放资源的新方式
try-with-resources:
1 2 3 4 5 6 7 try ( ){ }catch (Exception e){ e.printStackTrace(); }
什么是资源?
资源类一定是实现了Closeable接口,实现这个接口的类就是资源
有close()方法,try-with-resources会自动调用它的close()关闭资源。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 try ( InputStream is = new FileInputStream ("D:\\itcast\\图片资源\\meinv.jpg" ); OutputStream os = new FileOutputStream ("D:\\itcast\\meimei.jpg" ); ){ byte [] buffer = new byte [1024 ]; int len = 0 ; while ((len = is.read(buffer)) != -1 ){ os.write(buffer, 0 , len); } System.out.println("复制完成!" ); }catch (Exception e){ e.printStackTrace(); }
day10-缓冲流、转换流、序列流 第一章 字符流 FileReader
:文件字符输入流。
作用:以内存为基准,把磁盘文件的数据以字符的形式读入到内存。简单来说,读取文本文件内容到内存中去。
构造器:
public FileReader(File file)
:创建一个字符输入流与源文件对象接通。
public FileReader(String filePath)
:创建一个字符输入流与源文件路径接通。
方法:
public int read()
: 读取一个字符的编号返回! 读取完毕返回-1
public int read(char[] buffer)
:读取一个字符数组,读取多少个字符就返回多少个数量,读取完毕返回-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 31 Reader fr = new FileReader ("Day10Demo/src/dlei01.txt" );int ch ;while ((ch = fr.read()) != -1 ){ System.out.print((char )ch); }char [] buffer = new char [1024 ]; int len;while ((len = fr.read(buffer)) != -1 ) { System.out.print(new String (buffer, 0 , len)); }
FileWriter
文件字符输出流的使用。
作用:以内存为基准,把内存中的数据按照字符的形式写出到磁盘文件中去。简单来说,就是把内存的数据以字符写出到文件中去。
构造器:
public FileWriter(File file)
:创建一个字符输出流管道通向目标文件对象。
public FileWriter(String filePath)
:创建一个字符输出流管道通向目标文件路径。
public FileWriter(File file,boolean append
):创建一个追加数据的字符输出流管道通向目标文件对象。
public FileWriter(String filePath,boolean append)
:创建一个追加数据的字符输出流管道通向目标文件路径。
方法:
public void write(int c)
:写一个字符出去
public void write(String c)
写一个字符串出去:
public void write(char[] buffer)
:写一个字符数组出去
public void write(String c ,int pos ,int len)
:写字符串的一部分出去
public void write(char[] buffer ,int pos ,int len)
:写字符数组的一部分出去
小结:
字符输出流可以写字符数据出去,总共有5个方法写字符。
覆盖管道:
Writer fw = new FileWriter("Day10Demo/src/dlei03.txt"); // 覆盖数据管道
追加数据管道:
Writer fw = new FileWriter("Day10Demo/src/dlei03.txt",true); // 追加数据管道
换行:
fw.write("\r\n"); // 换行
读写字符文件数据建议使用字符流。
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 Writer fw = new FileWriter ("Day10Demo/src/dlei03.txt" ,true ); fw.write(97 ); fw.write('b' ); fw.write('磊' ); fw.write("\r\n" ); fw.write("Java是最优美的语言!" ); fw.write("我们在黑马学习它!" ); fw.write("\r\n" ); fw.write("我爱中国" .toCharArray()); fw.write("\r\n" ); fw.write("Java是最优美的语言!" ,0 ,9 ); fw.write("\r\n" ); fw.write("我爱中国" .toCharArray(),0 ,2 ); fw.write("\r\n" ); fw.close();
第二章 IO资源的处理 …
第三章 属性集 Properties
:属性集对象。
其实就是一个Map集合。也就是一个键值对集合。但是我们一般不会当集合使用,
因为有HashMap。
Properties
核心作用:
Properties代表的是一个属性文件,可以把键值对的数据存入到一个属性文件中去。
属性文件:后缀是.properties结尾的文件,里面的内容都是 key=value。
大家在后期学的很多大型框架技术中,属性文件都是很重要的系统配置文件。
1 2 3 users.properties admin=123456 dlei=dlei
需求:使用Properties对象生成一个属性文件,里面存入用户名和密码信息。
Properties的方法:
public Object setProperty(String key, String value)
: 保存一对属性。
public String getProperty(String key)
:使用此属性列表中指定的键搜索属性值
public Set<String> stringPropertyNames()
:所有键的名称的集合
public void store(OutputStream out, String comments)
:保存数据到属性文件中去
public void store(Writer fw, String comments)
:保存数据到属性文件中去
public synchronized void load(InputStream inStream)
:加载属性文件的数据到属性集对象中去
public synchronized void load(Reader fr)
:加载属性文件的数据到属性集对象中去
1 2 3 4 5 6 7 8 9 10 11 12 Properties properties = new Properties (); properties.setProperty("admin" , "123456" ); properties.setProperty("dlei" , "101333" ); System.out.println(properties);OutputStream os = new FileOutputStream ("Day10Demo/src/users.properties" ); properties.store(os , "i am very happy!!我快乐的保存了用户数据!" );
导入属性
1 2 3 4 5 6 7 8 9 10 Properties properties = new Properties (); System.out.println(properties); properties.load(new FileInputStream ("Day10Demo/src/users.properties" )); System.out.println(properties); System.out.println(properties.getProperty("dlei" )); System.out.println(properties.getProperty("admin" ));
第四章 缓冲流 缓冲流的概述和分类 什么是缓冲流:缓冲流可以提高字节流和字符流的读写数据的性能
缓冲流分为四类:
BufferedInputStream
:字节缓冲输入流,可以提高字节输入流读数据的性能。
BufferedOutStream
: 字节缓冲输出流,可以提高字节输出流写数据的性能。
BufferedReader
: 字符缓冲输入流,可以提高字符输入流读数据的性能。
BufferedWriter
: 字符缓冲输出流,可以提高字符输出流写数据的性能。
字节缓冲流 字节缓冲输入流:BufferedInputStream
作用:可以把低级的字节输入流包装成一个高级的缓冲字节输入流管道,从而提高字节输入流读数据的性能。
构造器: public BufferedInputStream(InputStream in)
原理:缓冲字节输入流管道自带了一个8KB的缓冲池,每次可以直接借用操作系统的功能最多提取8KB的数据到缓冲池中去,以后我们直接从缓冲池读取数据,所以性能较好!
字节缓冲输入流:BufferedInputStream
可以把低级的字节输入流包装成一个高级的缓冲字节输入流管道,从而提高字节输入流读数据的性能。
功能几乎无变化。
1 2 3 4 5 6 7 8 9 10 11 12 13 InputStream is = new FileInputStream ("Day10Demo/src/dlei04.txt" );BufferedInputStream bis = new BufferedInputStream (is);byte [] buffer = new byte [3 ];int len ;while ((len = is.read(buffer)) != -1 ){ String rs = new String (buffer, 0 , len); System.out.print(rs); }
字节缓冲输出流:BufferedOutputStream
作用:可以把低级的字节输出流包装成一个高级的缓冲字节输出流,从而提高写数据的性能。
构造器:public BufferedOutputStream(OutputStream os)
原理:缓冲字节输出流自带了8KB缓冲池,数据就直接写入到缓冲池中去,性能极高了!
字节缓冲输出流可以把低级的字节输出流包装成一个高级的缓冲字节输出流,从而提高写数据的性能。
功能几乎不变。
1 2 3 4 5 6 7 8 9 10 OutputStream os = new FileOutputStream ("Day10Demo/src/dlei05.txt" );BufferedOutputStream bos = new BufferedOutputStream (os); bos.write('a' ); bos.write(100 ); bos.write('b' ); bos.write("我爱中国" .getBytes()); bos.close();
高级的字节缓冲流按照一个一个字节数组的形式复制性能极高,建议以后使用
字符缓冲流 字符缓冲输入流:BufferedReader
作用:字符缓冲输入流可以把字符输入流包装成一个高级的缓冲字符输入流,可以提高字符输入流读数据的性能。
构造器:public BufferedReader(Reader reader)
原理:缓冲字符输入流默认会有一个8K的字符缓冲池,可以提高读字符的性能。缓冲字符输入流除了提高了字符输入流的读数据性能,缓冲字符输入流还多了一个按照行读取数据的功能(重点):public String readLine()
: 读取一行数据返回,读取完毕返回null
;
字符缓冲输入流可以把字符输入流包装成一个高级的缓冲字符输入流,
可以提高字符输入流读数据的性能。
除此之外多了一个按照行读取数据的功能:
public String readLine()
: 读取一行数据返回,读取完毕返回null
;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Reader fr = new FileReader ("Day10Demo/src/dlei06.txt" );BufferedReader br = new BufferedReader (fr); String line;while ((line = br.readLine())!=null ){ System.out.println(line); } br.close();
字符缓冲输出流:BufferedWriter
作用:把字符输出流包装成一个高级的缓冲字符输出流,提高写字符数据的性能。
构造器:public BufferedWriter(Writer writer)
:
原理:高级的字符缓冲输出流多了一个8k的字符缓冲池,写数据性能极大提高了!
字符缓冲输出流除了提高字符输出流写数据的性能,还多了一个换行的特有功能:public void newLine()
:新建一行。
缓冲字符输出流可以把低级的字符输出流进行包装。提高了写字符的性能。
多了一个换行的功能:public void newLine():新建一行。
1 2 3 4 5 6 7 8 9 10 11 12 13 Writer fw = new FileWriter ("Day10Demo/src/dlei07.txt" ,true );BufferedWriter bw = new BufferedWriter (fw); bw.write("我在黑马学IO流~~~~" ); bw.newLine(); bw.write("我在黑马学IO流~~~~" ); bw.newLine(); bw.close();
不同编码读取乱码问题
如果代码编码和读取的文件编码一致。字符流读取的时候不会乱码。
如果代码编码和读取的文件编码不一致。字符流读取的时候会乱码。
1 2 3 4 5 6 7 8 9 10 11 12 13 Reader fr = new FileReader ("D:\\itcast\\网络编程公开课\\Netty.txt" );BufferedReader br = new BufferedReader (fr); String line;while ((line = br.readLine())!=null ){ System.out.println(line); }
第五章 转换流 字符输入转换流InputStreamReader
作用
可以解决字符流读取不同编码乱码的问题。
可以把原始的字节流按照当前默认的代码编码转换成字符输入流。
也可以把原始的字节流按照指定编码转换成字符输入流
构造器:
public InputStreamReader(InputStream is):可以使用当前代码默认编码转换成字符流,几乎不用!
public InputStreamReader(InputStream is,String charset):可以指定编码把字节流转换成字符流
字符输入转换流可以把字节输入流按照默认编码转换成字符输入流。
Reader isr = new InputStreamReader(is); // 使用当前代码默认编码UTF-8转换成字符流,几乎不用!
字符输入转换流也可以指定编码把字节输入流转换成字符输入流。
Reader isr = new InputStreamReader(is,”GBK”); // 指定编码把字节流转换成字符流
字符输入转换流可以解决不同编码读取乱码的问题!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 InputStream is = new FileInputStream ("D:\\itcast\\网络编程公开课\\Netty.txt" );Reader isr = new InputStreamReader (is,"GBK" ); BufferedReader br = new BufferedReader (isr); String line;while ((line = br.readLine())!=null ){ System.out.println(line); }
字符输出转换流:OutputStreamWriter
作用:可以指定编码把字节输出流转换成字符输出流。可以指定写出去的字符的编码。
构造器:
public OutputStreamWriter(OutputStream os)
: 用当前默认编码UTF-8把字节输出流转换成字符输出流
public OutputStreamWriter(OutputStream os , String charset)
:指定编码把字节输出流转换成字符输出流
1 2 3 4 5 6 7 8 OutputStream os = new FileOutputStream ("Day10Demo/src/dlei07.txt" );Writer fw = new OutputStreamWriter (os,"GBK" ); fw.write("abc我是中国人" ); fw.close();
第六章 序列流 对象序列化 :就是把Java对象数据直接存储到文件中去。 对象 => 文件中
对象反序列化 :就是把Java对象的文件数据恢复到Java对象中。 文件中 => 对象
对象序列化流(对象字节输出流):ObjectOutputStream
作用 :把内存中的Java对象数据保存到文件中去。
构造器: public ObjectOutputStream(OutputStream out)
序列化方法:public final void writeObject(Object obj)
对象如果想参与序列化,对象必须实现序列化接口 implements Serializable ,否则序列化失败!
1 2 3 4 5 6 7 8 9 10 11 User user = new User ("tsgz" ,"003197" ,"铁扇公主" );OutputStream os = new FileOutputStream ("Day10Demo/src/obj.dat" );ObjectOutputStream oos = new ObjectOutputStream (os); oos.writeObject(user); oos.close(); System.out.println("序列化对象成功~~~~" );
对象反序列化(对象字节输入流):ObjectInputStream
作用:读取序列化的对象文件恢复到Java对象中。
构造器:public ObjectInputStream(InputStream is)
方法:public final Object readObject()
如果一个字段不想参数序列化:transient
修饰该成员变量,它将不参与序列化!
序列化版本号:
private static final long serialVersionUID = 2L;
必须序列化使用的版本号和反序列化使用的版本号一致才可以正常反序列化!否则报错!
1 2 3 4 5 6 7 8 InputStream is = new FileInputStream ("Day10Demo/src/obj.dat" );ObjectInputStream ois = new ObjectInputStream (is);User user = (User) ois.readObject(); System.out.println(user); System.out.println("反序列化完成!" );
第七章 打印流 打印流 PrintStream
/ PrintWriter
打印流的作用:
可以方便,快速的写数据出去。
可以实现打印啥出去,就是啥出去。
打印流的构造器:
public PrintStream(OutputStream os)
public PrintStream(String filepath)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 PrintStream ps = new PrintStream ("Day10Demo/src/dlei08.txt" ); ps.println(97 ); ps.println(110 ); ps.println("我在黑马快乐的调皮~~" ); ps.println(99.8 ); ps.println(false ); ps.println('徐' ); ps.close();
public static void setOut(PrintStream out)
:让系统的输出流向打印流。
1 2 3 4 5 6 7 8 9 System.out.println("==itheima0==" );PrintStream ps = new PrintStream ("Day10Demo/src/log.txt" ); System.setOut(ps); System.out.println("==itheima1==" ); System.out.println("==itheima2==" ); System.out.println("==itheima3==" ); System.out.println("==itheima4==" ); System.out.println("==itheima5==" );
day11-网络编程和NIO 第一章 网络编程 通信一定是基于软件结构实现的
C/S结构 :全称为Client/Server结构,是指客户端和服务器结构。常见程序有QQ、迅雷,IDEA等软件
B/S结构 :全称为Browser/Server结构,是指浏览器和服务器结构。常见浏览器有谷歌、火狐等、软件:博学谷、京东、淘宝。(开发中的重点,基于网页设计界面,界面效果可以更丰富: Java Web开发)
两种架构各有优势,但是无论哪种架构,都离不开网络的支持。网络编程,就是在一定的协议下,实现两台计算机的通信的技术
网络通信的三要素
协议:计算机网络客户端与服务端通信必须事先约定和彼此遵守的通信规则。 HTTP , FTP , TCP , UDP , SSH , SMTP。
IP地址:指互联网协议地址(Internet Protocol Address),俗称IP。
IP地址用来给一个网络中的计算机设备做唯一的编号
IPv4: 4个字节,32位组成 。 192.168.70.70
局域网:公司内部用
城域网
广域网(公网):可以在任何地方访问
IPv6: 可以实现为所有设备分配IP 128位
ipconfig:查看本机的IP
ping 检查本机与某个IP指定的机器是否联通,或者说是检测对方是否在线。
ping 空格 IP地址
ping 220.181.57.216
ping www.baidu.com
注意:特殊的IP地址: 本机IP地址.(不受环境的影响,任何时候都存在这两个ip,可以直接找本机!)
127.0.0.1 == localhost。
端口:端口号就可以唯一标识设备中的进程(应用程序)了
端口号:用两个字节表示的整数,它的取值范围是0~65535。
0~1023之间的端口号用于一些知名的网络服务和应用。
普通的应用程序需要使用1024以上的端口号。
如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。报出端口被占用异常!!
利用协议
+IP地址
+端口号
三元组合,就可以标识网络中的进程了,那么进程间的通信就可以利用这个标识与其它进程进行交互。
网络通信的分层和协议 网络通信协议:通信协议是对计算机必须遵守的规则,只有遵守这些规则,计算机之间才能进行通信
1 2 3 4 5 6 7 8 9 ------------------------------------------------------------------------------- 应用层 :应用程序(QQ,微信,浏览器),可能用到的协议(HTTP,FTP,SMTP) 通常程序员只需要关心这一层 ------------------------------------------------------------------------------ 传输层 :TCP/IP协议 - UDP协议 计算机网络工程师需要精通的协议,有些技术我们也需要精通这一层协议, ----------------------------------------------------------------- 网络层 :IP协议 封装自己的IP和对方的IP和端口 ----------------------------------------------------------------- 数据链路层 : 进入到硬件(网) -----------------------------------------------------------------
InetAddress类概述 InetAddress
类的对象就代表一个IP地址对象。
InetAddress
类成员方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 InetAddress ip = InetAddress.getLocalHost(); System.out.println(ip.getHostName()); System.out.println(ip.getHostAddress());InetAddress ip2 = InetAddress.getByName("www.baidu.com" ); System.out.println(ip2.getHostName()); System.out.println(ip2.getHostAddress());InetAddress ip3 = InetAddress.getByName("182.61.200.6" ); System.out.println(ip3.getHostName()); System.out.println(ip3.getHostAddress()); System.out.println(ip2.isReachable(5000 ));
第二章 UDP通信 UDP协议的特点
面向无连接 的协议
发送端只管发送,不确认对方是否能收到
基于数据包进行数据传输
发送数据的包的大小限制64KB以内
因为面向无连接,速度快,但是不可靠。会丢失数据!
UDP协议的使用场景
UDP协议相关的两个类
DatagramPacket
数据包对象
作用:用来封装要发送或要接收的数据,比如:集装箱
DatagramSocket
DatagramPacket
类构造器
发送端用:new DatagramPacket(byte[] buf, int length, InetAddress address, int port)
创建发送端数据包对象
buf
:要发送的内容,字节数组
length
:要发送内容的长度,单位是字节
address
:接收端的IP地址对象
port
:接收端的端口号
接收端用:new DatagramPacket(byte[] buf, int length)
创建接收端的数据包对象
buf
:用来存储接收到内容
length
:能够接收内容的长度
DatagramPacket
类常用方法
* `int getLength()` 获得实际接收到的字节个数
DatagramSocket
类构造方法
DatagramSocket()
创建发送端的Socket对象,系统会随机分配一个端口号。
DatagramSocket(int port)
创建接收端的Socket对象并指定端口号
DatagramSocket
类成员方法
void send(DatagramPacket dp)
发送数据包
void receive(DatagramPacket p)
接收数据包
需求:使用UDP实现客户端发,服务端收。(了解)
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 System.out.println("===启动客户端===" );byte [] buffer = "今晚,约吗?" .getBytes();DatagramPacket packet = new DatagramPacket (buffer, buffer.length, InetAddress.getLocalHost(), 6666 );DatagramSocket socket = new DatagramSocket (); socket.send(packet); socket.close();
服务端
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 System.out.println("==启动服务端程序==" );byte [] buffer = new byte [1024 *64 ];DatagramPacket packet = new DatagramPacket (buffer, buffer.length);DatagramSocket socket = new DatagramSocket (6666 ); socket.receive(packet);int len = packet.getLength();String rs = new String (buffer , 0 , len); System.out.println(rs);String ip = packet.getAddress().getHostAddress();int port = packet.getPort(); System.out.println("对方:" +ip+":" +port); socket.close();
第三章 TCP通信 TCP/IP协议 ==> Transfer Control Protocol ==> 传输控制协议 TCP/IP协议的特点
面向连接 的协议
只能由客户端主动发送数据给服务器端,服务器端接收到数据之后,可以给客户端响应数据。
通过三次握手建立连接 ,连接成功形成数据传输通道。
通过四次挥手断开连接
基于IO流进行数据传输
传输数据大小没有限制
因为面向连接的协议,速度慢,但是是可靠的协议 。
TCP协议的使用场景
TCP协议相关的类
Socket
一个该类的对象就代表一个客户端程序
ServerSocket
一个该类的对象就代表一个服务器端程序
TCP通信也叫Socket
网络编程,只要代码基于Socket
开发,底层就是基于了可靠传输的TCP通信。
Socket
类构造方法
* `Socket(String host, int port)`
根据ip地址字符串和端口号创建客户端Socket对象
> 只要执行该方法,就会立即连接指定的服务器程序,如果连接不成功,则会抛出异常。如果连接成功,则表示三次握手通过。
Socket
类常用方法
OutputStream getOutputStream()
; 获得字节输出流对象
InputStream getInputStream()
;获得字节输入流对象
客户端的开发流程
客户端要请求于服务端的socket
管道连接。
从socket
通信管道中得到一个字节输出流
通过字节输出流给服务端写出数据。
服务端的开发流程
注册端口。
接收客户端的Socket
管道连接。
从socket
通信管道中得到一个字节输入流。
从字节输入流中读取客户端发来的数据。
需求:客户端发送一行数据,服务端接收一行数据!!
客户端用Socket
连接服务端。
服务端用ServerSocket
注册端口,接收客户端的Socket
连接。
通信是很严格的,对方怎么发,你就应该怎么收,对方发多少你就只能收多少。
实现的面向连接的socket
端到端的通信管道,一方如果出现对象,另一方会出现异常!
TCP通信的第一个入门案例 简单的发送一条信息
客户端
1 2 3 4 5 6 7 8 9 10 11 Socket socket = new Socket ("127.0.0.1" , 9999 );OutputStream os = socket.getOutputStream();PrintStream ps = new PrintStream (os); ps.println("我是客户端,喜欢你很久了,第一次给你发消息,只想说:约吗?" ); ps.flush(); System.out.println("客户端发送完毕~~~~" );
服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 System.out.println("----服务端启动----" );ServerSocket serverSocket = new ServerSocket (9999 );Socket socket = serverSocket.accept();InputStream is = socket.getInputStream();Reader isr = new InputStreamReader (is);BufferedReader br = new BufferedReader (isr); String line ;if ((line = br.readLine())!=null ){ System.out.println(line); }
TCP通信的第二个案例-循环发送 客户端可以反复发送数据,服务端可以反复接受数据
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Socket socket = new Socket ("127.0.0.1" , 9999 );OutputStream os = socket.getOutputStream();PrintStream ps = new PrintStream (os);while (true ){Scanner sc = new Scanner (System.in); System.out.print("请说:" ); ps.println(sc.nextLine()); ps.flush(); }
服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 System.out.println("----服务端启动----" );ServerSocket serverSocket = new ServerSocket (9999 );Socket socket = serverSocket.accept();InputStream is = socket.getInputStream();Reader isr = new InputStreamReader (is);BufferedReader br = new BufferedReader (isr); String line ;while ((line = br.readLine())!=null ){ System.out.println(line); }
TCP通信的第三个案例-一个服务端同时接受多个客户端消息 需要在服务端引入多线程。每接收一个客户端的Socket通道,就为它分配一个独立的线程来处理它的消息。如此便可实现:一个服务端可以同时接收多个客户端的消息。
服务端
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 public class ServerDemo02 { public static void main (String[] args) throws Exception { System.out.println("----服务端启动----" ); ServerSocket serverSocket = new ServerSocket (9999 ); while (true ){ Socket socket = serverSocket.accept(); new ServerReaderThread (socket).start(); } } }class ServerReaderThread extends Thread { private Socket socket ; public ServerReaderThread (Socket socket) { this .socket = socket; } @Override public void run () { try { InputStream is = socket.getInputStream(); Reader isr = new InputStreamReader (is); BufferedReader br = new BufferedReader (isr); String line ; while ((line = br.readLine())!=null ){ System.out.println(socket.getRemoteSocketAddress()+"说:" +line); } }catch (Exception e){ System.out.println(socket.getRemoteSocketAddress()+"下线了~~~~~~" ); } } }
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Socket socket = new Socket ("127.0.0.1" , 9999 );OutputStream os = socket.getOutputStream();PrintStream ps = new PrintStream (os);while (true ){ Scanner sc = new Scanner (System.in); System.out.print("请说:" ); ps.println(sc.nextLine()); ps.flush(); }
TCP通信的第四个案例-线程池 我们之前引入的线程解决一个服务端可以接收多个客户端消息。客户端与服务端的线程模型是: N-N的关系。 一个客户端要一个线程。这种模型是不行的,并发越高,系统瘫痪的越快
我们可以在服务端引入线程池 ,使用线程池来处理与客户端的消息通信,线程池不会引起出现过多的线程而导致系统死机
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 try { Socket socket = new Socket ("127.0.0.1" , 9999 ); OutputStream os = socket.getOutputStream(); PrintStream ps = new PrintStream (os); Scanner sc = new Scanner (System.in); while (true ){ System.out.print("请说:" ); String msg = sc.nextLine(); ps.println(msg); ps.flush(); } } catch (Exception e) { e.printStackTrace(); }
服务端主程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 try { System.out.println("----------服务端启动成功------------" ); ServerSocket ss = new ServerSocket (9999 ); HandlerSocketThreadPool handlerSocketThreadPool = new HandlerSocketThreadPool (3 , 100 ); while (true ){ Socket socket = ss.accept() ; System.out.println("有人上线了!!" ); handlerSocketThreadPool.execute(new ReaderClientRunnable (socket)); } } catch (Exception e) { e.printStackTrace(); }
HandlerSocketThreadPool
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class HandlerSocketThreadPool { private ExecutorService executor; public HandlerSocketThreadPool (int maxPoolSize, int queueSize) { executor = new ThreadPoolExecutor ( maxPoolSize, maxPoolSize, 120L , TimeUnit.SECONDS, new ArrayBlockingQueue <Runnable>(queueSize) ); } public void execute (Runnable task) { this .executor.execute(task); } }
ReaderClientRunnable
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 class ReaderClientRunnable implements Runnable { private Socket socket ; public ReaderClientRunnable (Socket socket) { this .socket = socket; } @Override public void run () { try { InputStream is = socket.getInputStream() ; Reader fr = new InputStreamReader (is); BufferedReader br = new BufferedReader (fr); String line = null ; while ((line = br.readLine())!=null ){ System.out.println("服务端收到了数据:" +line); } } catch (Exception e) { System.out.println("有人下线了" ); } } }
即时通信 ClientChat
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 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 import javax.swing.*;import java.awt.*;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import java.io.DataInputStream;import java.io.DataOutputStream;import java.net.Socket;public class ClientChat implements ActionListener { private JFrame win = new JFrame (); public JTextArea smsContent = new JTextArea (23 , 50 ); private JTextArea smsSend = new JTextArea (4 ,40 ); public JList<String> onLineUsers = new JList <>(); private JCheckBox isPrivateBn = new JCheckBox ("私聊" ); private JButton sendBn = new JButton ("发送" ); private JFrame loginView; private JTextField ipEt , nameEt , idEt; private Socket socket ; public static void main (String[] args) { new ClientChat ().initView(); } private void initView () { win.setSize(650 , 600 ); displayLoginView(); } private void displayChatView () { JPanel bottomPanel = new JPanel (new BorderLayout ()); win.add(bottomPanel, BorderLayout.SOUTH); bottomPanel.add(smsSend); JPanel btns = new JPanel (new FlowLayout (FlowLayout.LEFT)); btns.add(sendBn); btns.add(isPrivateBn); bottomPanel.add(btns, BorderLayout.EAST); smsContent.setBackground(new Color (0xdd ,0xdd ,0xdd )); win.add(new JScrollPane (smsContent), BorderLayout.CENTER); smsContent.setEditable(false ); Box rightBox = new Box (BoxLayout.Y_AXIS); onLineUsers.setFixedCellWidth(120 ); onLineUsers.setVisibleRowCount(13 ); rightBox.add(new JScrollPane (onLineUsers)); win.add(rightBox, BorderLayout.EAST); win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); win.pack(); setWindowCenter(win,650 ,600 ,true ); sendBn.addActionListener(this ); } private void displayLoginView () { loginView = new JFrame ("登录" ); loginView.setLayout(new GridLayout (3 , 1 )); loginView.setSize(400 , 230 ); JPanel ip = new JPanel (); JLabel label = new JLabel (" IP:" ); ip.add(label); ipEt = new JTextField (20 ); ip.add(ipEt); loginView.add(ip); JPanel name = new JPanel (); JLabel label1 = new JLabel ("姓名:" ); name.add(label1); nameEt = new JTextField (20 ); name.add(nameEt); loginView.add(name); JPanel btnView = new JPanel (); JButton login = new JButton ("登陆" ); btnView.add(login); JButton cancle = new JButton ("取消" ); btnView.add(cancle); loginView.add(btnView); loginView.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setWindowCenter(loginView,400 ,260 ,true ); login.addActionListener(this ); cancle.addActionListener(this ); } private static void setWindowCenter (JFrame frame, int width , int height, boolean flag) { Dimension ds = frame.getToolkit().getScreenSize(); int width1 = ds.width; int height1 = ds.height ; System.out.println(width1 +"*" + height1); frame.setLocation(width1/2 - width/2 , height1/2 -height/2 ); frame.setVisible(flag); } @Override public void actionPerformed (ActionEvent e) { JButton btn = (JButton) e.getSource(); switch (btn.getText()){ case "登陆" : String ip = ipEt.getText().toString(); String name = nameEt.getText().toString(); String msg = "" ; if (ip==null || !ip.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}" )){ msg = "请输入合法的服务端ip地址" ; }else if (name==null || !name.matches("\\S{1,}" )){ msg = "姓名必须1个字符以上" ; } if (!msg.equals("" )){ JOptionPane.showMessageDialog(loginView, msg); }else { try { win.setTitle(name); socket = new Socket (ip, Constants.PORT); new ClientReader (this ,socket).start(); DataOutputStream dos = new DataOutputStream (socket.getOutputStream()); dos.writeInt(1 ); dos.writeUTF(name.trim()); dos.flush(); loginView.dispose(); displayChatView(); } catch (Exception e1) { e1.printStackTrace(); } } break ; case "取消" : System.exit(0 ); break ; case "发送" : String msgSend = smsSend.getText().toString(); if (!msgSend.trim().equals("" )){ try { String selectName = onLineUsers.getSelectedValue(); int flag = 2 ; if (selectName!=null &&!selectName.equals("" )){ msgSend =("@" +selectName+"," +msgSend); if (isPrivateBn.isSelected()){ flag = 3 ; } } DataOutputStream dos = new DataOutputStream (socket.getOutputStream()); dos.writeInt(flag); dos.writeUTF(msgSend); if (flag == 3 ){ dos.writeUTF(selectName.trim()); } dos.flush(); } catch (Exception e1) { e1.printStackTrace(); } } smsSend.setText(null ); break ; } } }class ClientReader extends Thread { private Socket socket; private ClientChat clientChat ; public ClientReader (ClientChat clientChat, Socket socket) { this .clientChat = clientChat; this .socket = socket; } @Override public void run () { try { DataInputStream dis = new DataInputStream (socket.getInputStream()); while (true ){ int flag = dis.readInt(); if (flag == 1 ){ String nameDatas = dis.readUTF(); String[] names = nameDatas.split(Constants.SPILIT); clientChat.onLineUsers.setListData(names); }else if (flag == 2 ){ String msg = dis.readUTF() ; clientChat.smsContent.append(msg); clientChat.smsContent.setCaretPosition(clientChat.smsContent.getText().length()); } } } catch (Exception e) { e.printStackTrace(); } } }
User
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 public class User { private Integer id ; private String name ; public User (Integer id, String name) { this .id = id; this .name = name; } public Integer getId () { return id; } public void setId (Integer id) { this .id = id; } public String getName () { return name; } public void setName (String name) { this .name = name; } @Override public String toString () { return "User [id=" + id + ", name=" + name + "]" ; } }
ServerChat
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 public class ServerChat { public static Map<Socket, String> onLineSockets = new HashMap <>(); public static void main (String[] args) { try { ServerSocket serverSocket = new ServerSocket (Constants.PORT); while (true ){ Socket socket = serverSocket.accept(); new ServerReader (socket).start(); } } catch (Exception e) { e.printStackTrace(); } } }class ServerReader extends Thread { private Socket socket; public ServerReader (Socket socket) { this .socket = socket; } @Override public void run () { DataInputStream dis = null ; try { dis = new DataInputStream (socket.getInputStream()); while (true ){ int flag = dis.readInt(); if (flag == 1 ){ String name = dis.readUTF() ; System.out.println(name+"---->" +socket.getRemoteSocketAddress()); ServerChat.onLineSockets.put(socket, name); } writeMsg(flag,dis); } } catch (Exception e) { System.out.println("--有人下线了--" ); ServerChat.onLineSockets.remove(socket); try { writeMsg(1 ,dis); } catch (Exception e1) { e1.printStackTrace(); } } } private void writeMsg (int flag, DataInputStream dis) throws Exception { String msg = null ; if (flag == 1 ){ StringBuilder rs = new StringBuilder (); Collection<String> onlineNames = ServerChat.onLineSockets.values(); if (onlineNames != null && onlineNames.size() > 0 ){ for (String name : onlineNames){ rs.append(name+ Constants.SPILIT); } msg = rs.substring(0 , rs.lastIndexOf(Constants.SPILIT)); sendMsgToAll(flag,msg); } }else if (flag == 2 || flag == 3 ){ String newMsg = dis.readUTF() ; String sendName = ServerChat.onLineSockets.get(socket); StringBuilder msgFinal = new StringBuilder (); SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss EEE" ); if (flag == 2 ){ msgFinal.append(sendName).append(" " ).append(sdf.format(System.currentTimeMillis())).append("\r\n" ); msgFinal.append(" " ).append(newMsg).append("\r\n" ); sendMsgToAll(flag,msgFinal.toString()); }else if (flag == 3 ){ msgFinal.append(sendName).append(" " ).append(sdf.format(System.currentTimeMillis())).append("对您私发\r\n" ); msgFinal.append(" " ).append(newMsg).append("\r\n" ); String destName = dis.readUTF(); sendMsgToOne(destName,msgFinal.toString()); } } } private void sendMsgToOne (String destName, String msg) throws Exception { Set<Socket> allOnLineSockets = ServerChat.onLineSockets.keySet(); for (Socket sk : allOnLineSockets){ if (ServerChat.onLineSockets.get(sk).trim().equals(destName)){ DataOutputStream dos = new DataOutputStream (sk.getOutputStream()); dos.writeInt(2 ); dos.writeUTF(msg); dos.flush(); } } } private void sendMsgToAll (int flag, String msg) throws Exception { Set<Socket> allOnLineSockets = ServerChat.onLineSockets.keySet(); for (Socket sk : allOnLineSockets){ DataOutputStream dos = new DataOutputStream (sk.getOutputStream()); dos.writeInt(flag); dos.writeUTF(msg); dos.flush(); } } }
Constants
1 2 3 4 5 6 7 public class Constants { public static final int PORT = 7778 ; public static final String SPILIT = "003197♣♣㏘♣④④♣" ; }
文件上传 实现客户端上传图片给服务端保存起来
服务端实现:
接受多个客户端传输来的图片数据存储到服务器路径
响应一个成功的消息给当前客户端
ClientDemo
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 ClientDemo { public static void main (String[] args) throws Exception { Socket socket = new Socket (Constants.SERVER_IP , Constants.SERVER_PORT); BufferedOutputStream bos = new BufferedOutputStream (socket.getOutputStream()); BufferedInputStream bis = new BufferedInputStream (new FileInputStream (Constants.SRC_IMAGE)); byte [] buffer = new byte [1024 ]; int len ; while ((len = bis.read(buffer)) != -1 ) { bos.write(buffer, 0 ,len); } bos.flush(); socket.shutdownOutput(); bis.close(); BufferedReader br = new BufferedReader (new InputStreamReader (socket.getInputStream())); System.out.println("收到服务端响应:" +br.readLine()); } }
Constants
1 2 3 4 5 6 7 8 9 10 public class Constants { public static final String SRC_IMAGE = "D:\\itcast\\图片资源\\beautiful.jpg" ; public static final String SERVER_DIR = "D:\\itcast\\约吧图片服务器\\" ; public static final String SERVER_IP = "127.0.0.1" ; public static final int SERVER_PORT = 8888 ; }
ServerDemo
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 ServerDemo { public static void main (String[] args) throws Exception { System.out.println("----服务端启动----" ); ServerSocket serverSocket = new ServerSocket (Constants.SERVER_PORT); while (true ){ Socket socket = serverSocket.accept(); new ServerReaderThread (socket).start(); } } }class ServerReaderThread extends Thread { private Socket socket ; public ServerReaderThread (Socket socket) { this .socket = socket; } @Override public void run () { try { InputStream is = socket.getInputStream(); BufferedInputStream bis = new BufferedInputStream (is); BufferedOutputStream bos = new BufferedOutputStream (new FileOutputStream (Constants.SERVER_DIR+ UUID.randomUUID().toString()+".jpg" )); byte [] buffer = new byte [1024 ]; int len ; while ((len = bis.read(buffer)) != -1 ) { bos.write(buffer, 0 ,len); } bos.close(); System.out.println("服务端接收完毕了!" ); PrintStream ps = new PrintStream (socket.getOutputStream()); ps.println("您好,已成功接收您上传的图片!" ); ps.flush(); Thread.sleep(100000 ); }catch (Exception e){ System.out.println(socket.getRemoteSocketAddress()+"下线了~~~~~~" ); } } }
第四章 BS架构 之前客户端和服务端都需要自己开发。也就是CS架构。接下来模拟一下BS架构。
客户端:浏览器。(无需开发)
服务端:自己开发。
需求:在浏览器中请求本程序,响应一个网页文字给浏览器显示。
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 class ServerReaderThread extends Thread { private Socket socket; public ServerReaderThread (Socket socket) { this .socket = socket; } @Override public void run () { try { PrintStream ps = new PrintStream (socket.getOutputStream()); ps.println("HTTP/1.1 200 OK" ); ps.println("Content-Type:text/html;charset=UTF-8" ); ps.println(); ps.println("<span style='color:green;font-size:100px;'>Hello, world<span>" ); Thread.sleep(4000 ); ps.close(); } catch (Exception e) { e.printStackTrace(); } } }
第五章 基本通信模型
BIO通信模式:同步阻塞式通信。(Socket网络编程也就是上面的通信架构)
同步:当前线程要自己进行数据的读写操作。(自己去银行取钱)
异步: 当前线程可以去做其他事情,(委托一小弟拿银行卡到银行取钱,然后给你)
阻塞: 在数据没有的情况下,还是要继续等待着读。(排队等待)
非阻塞:在数据没有的情况下,会去做其他事情,一旦有了数据再来获取。(柜台取款,取个号,然后坐在椅子上做其它事,等号广播会通知你办理)
BIO表示同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
同步阻塞式性能极差:大量线程,大量阻塞。
伪异步通信:引入了线程池。
不需要一个客户端一个线程,可以实现1个线程复用来处理很多个客户端!
这种架构,可以避免系统的死机,因为不会出现很多线程,线程可控。
但是高并发下性能还是很差:a.线程数量少,数据依然是阻塞的。数据没有来线程还是要等待!
NIO表示同步非阻塞IO,服务器实现模式为请求对应一个线程,
即客户端发送的连接请求都会注册到多路复用器上,
多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
1个主线程专门负责接收客户端:
1个线程[c1 ,s2 ,c3,c4, ,s2 ,c3,c4,,c3,c4, ,s2 ,c3,c4]轮询所有的客户端,发来了数据才会开启线程处理
这种架构性能还可以!!
同步 :线程还是要不断的接收客户端连接,以及处理数据。
非阻塞 :如果一个管道没有数据,不需要等待,可以轮询下一个管道是否有数据!
AIO表示异步非阻塞IO,服务器实现模式为一个有效请求一个线程,
客户端的I/O请求都是由操作系统先完成IO操作后再通知服务器应用来启动线程进行处理。
异步:服务端线程接收到了客户端管道以后就交给底层处理它的io通信。
自己可以做其他事情。
非阻塞:底层也是客户端有数据才会处理,有了数据以后处理好通知服务器应用来启动线程进行处理。
各种模型应用场景:
BIO适用于连接数目比较小且固定的架构,该方式对服务器资源要求比较高,JDK 1.4以前的唯一选择。
NIO适用于连接数目多且连接比较短(轻操作)的架构,如聊天服务器,编程复杂,
JDK 1.4开始支持。
AIO适用于连接数目多且连接比较长(重操作)的架构,如相册服务器,充分调用操作系统参与并发操作,编程复杂,JDK 1.7开始支持。
day12-JUnit单元测试、反射、注解、动态代理 第一章 单元测试 单元测试是指程序员写的测试代码给自己的类中的方法进行预期正确性的验证。
单元测试一旦写好了这些测试代码,就可以一直使用,可以实现一定程度上的自动化测试。
单元测试一般要使用框架进行。 什么是框架? 框架是前人或者一些牛逼的技术公司在实战或者研发中设计的一些优良的设计方案或者成型的 代码功能,作为一个完整的技术体系发行出来称为框架。
框架可以让程序员快速拥有一个强大的解决方案,可以快速的开发功能,提高效率并且直接就有了很好的性能。
单元测试的经典框架:Junit
Junit
是什么
Junit
是Java语言编写的第三方单元测试框架
Junit
框架的方案可以帮助我们方便且快速的测试我们的代码的正确性。
单元测试概念
单元:在Java中,一个类就是一个单元
单元测试:程序猿用Junit
编写的一小段代码,用来对某个类中的某个方法进行功能测试或业务逻辑测试。
Junit
单元测试框架的作用
用来对类中的方法功能进行有目的的测试,以保证程序的正确性和稳定性。
能够独立的测试某个方法或者所有方法的预期正确性。
Junit框架的使用步骤:
下载这个框架。(别人设计好的技术体系)
框架一般是jar包的形式,jar包里面都是class文件。(Java工程的最终形式)class文件就是我们调用的核心代码
现在不需要大家去官网下载,因为很多知名框架其实IDEA工具早就整合好了,程序员可以直接使用。
Junit已经被IDEA下载好了,可以直接导入到项目使用的。
直接用Junit测试代码即可
先模拟业务代码
写测试类
测试类的命名规范:以Test开头,以业务类类名结尾,使用驼峰命名法
业务名称是:UserService
测试这个业务类的测试类:TestUserService/UserServiceTest
在测试类中写测试方法
测试方法的命名规则:以test开头,以业务方法名结尾
比如被测试业务方法名为:login,那么测试方法名就应该叫:testLogin
测试方法注意事项
必须是public
修饰的,没有返回值,没有参数
必须使注解@Test
修饰
如何运行测试方法
选中方法名 –> 右键 –> Run ‘测试方法名’ 运行选中的测试方法
选中测试类名 –> 右键 –> Run ‘测试类类名’ 运行测试类中所有测试方法
选中模块名 –> 右键 –> Run ‘All Tests’ 运行模块中的所有测试类的所有测试方法
如何查看测试结果
Junit
常用注解(Junit 4.xxxx版本)
@Test
测试方法!
@Before
:用来修饰实例方法,该方法会在每一个测试方法执行之前执行一次。
@After
:用来修饰实例方法,该方法会在每一个测试方法执行之后执行一次。
@BeforeClass
:用来静态修饰方法,该方法会在所有测试方法之前只执行一次。
@AfterClass
:用来静态修饰方法,该方法会在所有测试方法之后只执行一次。
开始执行的方法:初始化资源。
执行完之后的方法:释放资源。
Junit常用注解(Junit5.xxxx版本)
@Test
测试方法!
@BeforeEach
:用来修饰实例方法,该方法会在每一个测试方法执行之前执行一次。
@AfterEach
:用来修饰实例方法,该方法会在每一个测试方法执行之后执行一次。
@BeforeAll
:用来静态修饰方法,该方法会在所有测试方法之前只执行一次。
@AfterAll
:用来静态修饰方法,该方法会在所有测试方法之后只执行一次。
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 UserServiceTest { @Before public void before () { System.out.println("===before===" ); } @After public void after () { System.out.println("===after===" ); } @BeforeClass public static void beforeClass () { System.out.println("===beforeClass===" ); } @AfterClass public static void afterClass () { System.out.println("===afterClass===" ); } @Test public void testLogin () { UserService userService = new UserService (); String rs = userService.login("admin" ,"123456" ); Assert.assertEquals("登录业务功能方法有错误,请检查!" ,"success" ,rs); } @Test public void testChu () { UserService userService = new UserService (); userService.chu(10 , 2 ); } }
第二章 反射 反射的概述 反射,注解,代理,泛型是Java的高级技术,是以后框架的底层原理必须使用到的技术。
反射:是Java独有的技术。是Java技术显著的特点。
反射是指对于任何一个类,在”运行的时候”都可以直接得到这个类全部成分。
在运行时,可以直接得到这个类的构造器对象。(Constructor)
在运行时,可以直接得到这个类的成员变量对象。(Field)
在运行时,可以直接得到这个类的成员方法对象。(Method)
反射的核心思想和关键就是得到:编译以后的class文件对象。
反射提供了一个Class类型,就是可以得到编译以后的class类对象。
1 2 3 HelloWorld.java -> javac -> HelloWorld.class Class c = HelloWorld.class;
反射是工作在运行时的技术,因为只有运行之后才会有class类对象
反射的核心思想和关键就是得到:编译以后的class文件对象
反射是在运行时获取类的字节码文件对象:然后可以解析类中的全部成分
获取Class类对象 反射是通过先得到编译以后的Class类对象:字节码文件。然后才可以得到类中的全部成分,进行一些功能设计。
反射为一个类的全部成分都设计了一个类型来代表这个对象:
Class
: 字节码文件的类型
Constructor
: 构造器的类型
Field
: 成员变量的类型
Method
: 方法的类型
反射技术的第一步永远是先得到Class类对象:有三种方式获取
类名.class
通过类的对象.getClass()方法
Class.forName(“类的全限名”)
1 public static Class<?> forName(String className)
Class类下的方法:
String getSimpleName(); 获得类名字符串:类名
String getName(); 获得类全名:包名+类名
T newInstance() ; 创建Class对象关联类的对象,其实底层也是调用无参数构造器,已经被淘汰。
Class类对象的获取有三种方式:
类名.class。
通过类的对象.getClass()方法。
Class.forName(“类的全限名”)。
Class类的方法:
String getSimpleName();
获得类名字符串:类名
String getName();
获得类全名:包名+类名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Class c1 = Student.class; System.out.println(c1);Student swk = new Student ();Class c2 = swk.getClass(); System.out.println(c2);Class c3 = Class.forName("com.itheima._03反射_获取Class类对象.Student" ); System.out.println(c3); System.out.println(c1.getSimpleName()); System.out.println(c1.getName());
获取Constructor构造器对象 反射中Class类型获取构造器提供了很多的API: 1. Constructor getConstructor(Class... parameterTypes)
根据参数匹配获取某个构造器,只能拿public修饰的构造器,几乎不用! 2. Constructor getDeclaredConstructor(Class... parameterTypes)
根据参数匹配获取某个构造器,只要申明就可以定位,不关心权限修饰符,建议使用! 3. Constructor[] getConstructors()
获取所有的构造器,只能拿public修饰的构造器。几乎不用!!太弱了! 4. Constructor[] getDeclaredConstructors()
获取所有声明的构造器,只要你写我就能拿到,无所谓权限。建议使用!!
获取类的全部构造器对象: Constructor[] getDeclaredConstructors()
获取所有申明的构造器,只要你写我就能拿到,无所谓权限。建议使用!!
获取类的某个构造器对象:Constructor getDeclaredConstructor(Class... parameterTypes)
根据参数匹配获取某个构造器,只要申明就可以定位,不关心权限修饰符,建议使用!
对于类Student
来说
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Student { private String name ; private int age ; private Student () { System.out.println("无参数构造器被执行~~~~" ); } public Student (String name, int age) { System.out.println("有参数构造器被执行~~~~" ); this .name = name; this .age = age; } }
获取该类运行时的构造器可以用一下代码实现
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 TestStudent { @Test public void getConstructors () { Class c = Student.class ; Constructor[] cons = c.getConstructors(); for (Constructor con : cons) { System.out.println(con.getName()+"===>" +con.getParameterCount()); } } @Test public void getDeclaredConstructors () { Class c = Student.class ; Constructor[] cons = c.getDeclaredConstructors(); for (Constructor con : cons) { System.out.println(con.getName()+"===>" +con.getParameterCount()); } } @Test public void getConstructor () throws Exception { Class c = Student.class ; Constructor con = c.getConstructor(String.class ,int .class); System.out.println(con.getName()+"===>" +con.getParameterCount()); } @Test public void getDeclaredConstructor () throws Exception { Class c = Student.class ; Constructor con = c.getDeclaredConstructor(); System.out.println(con.getName()+"===>" +con.getParameterCount()); } }
获取到构造器之后,可以通过该构造器初始化对象
反射获取Class
中构造器对象Constructor
的作用,就是用来初始化并得到类的一个对象返回
Constructor
的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 public class TestStudent02 { @Test public void createObj01 () throws Exception { Class c = Student.class ; Constructor constructor = c.getDeclaredConstructor(); constructor.setAccessible(true ); Student swk = (Student) constructor.newInstance(); System.out.println(swk); } @Test public void createObj02 () throws Exception { Class c = Student.class ; Constructor constructor = c.getDeclaredConstructor(String.class , int .class); Student swk = (Student) constructor.newInstance("孙悟空" ,10000 ); System.out.println(swk); } }
获取Field成员变量对象
Field getField(String name);
根据成员变量名获得对应Field对象,只能获得public修饰
Field getDeclaredField(String name);
根据成员变量名获得对应Field对象,只要申明了就可以得到
Field[] getFields();
获得所有的成员变量对应的Field对象,只能获得public的
Field[] getDeclaredFields();
获得所有的成员变量对应的Field对象,只要申明了就可以得到
对于Dog
类
1 2 3 4 5 6 7 8 9 10 11 public class Dog { private String name; private int age ; private String color ; public static String school; public static final String SCHOOL_1 = "宠物学校" ; public Dog () { } }
获取其成员变量
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 public class FieldDemo { @Test public void getDeclaredFields () { Class c = Dog.class; Field[] fields = c.getDeclaredFields(); for (Field field : fields) { System.out.println(field.getName()+"===>" +field.getType()); } } @Test public void getDeclaredField () throws Exception { Class c = Dog.class; Field ageF = c.getDeclaredField("age" ); System.out.println(ageF.getName()+"--->" +ageF.getType()); } }
给成员变量取值和复制
void set(Object obj, Object value)
:给对象注入某个成员变量数据
Object get(Object obj)
:获取对象的成员变量的值。
void setAccessible(true)
:暴力反射,设置为可以直接访问私有类型的属性。
Class getType()
:获取属性的类型,返回Class对象。
String getName()
:获取属性的名称。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class FieldDemo02 { @Test public void setField () throws Exception { Class c = Dog.class ; Field nameF = c.getDeclaredField("name" ); Dog taiDi = new Dog (); nameF.setAccessible(true ); nameF.set(taiDi , "勇敢的泰迪" ); System.out.println(taiDi); String value = nameF.get(taiDi)+"" ; System.out.println(value); } }
获取Method方法 反射获取类的Method方法对象:
Method getMethod(String name,Class...args)
:根据方法名和参数类型获得对应的方法对象,只能获得public的
Method getDeclaredMethod(String name,Class...args)
:根据方法名和参数类型获得对应的方法对象,包括private的
Method[] getMethods()
:获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的
Method[] getDeclaredMethods()
:获得类中的所有成员方法对象,返回数组,只获得本类申明的方法
Method的方法执行:Object invoke(Object obj, Object... args)
参数一:触发的是哪个对象的方法执行
参数二: args:调用方法时传递的实际参数
Dog
类
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 class Dog { private String name ; public Dog () { } public Dog (String name) { this .name = name; } public void run () { System.out.println("狗跑的贼快~~" ); } private void eat () { System.out.println("狗吃骨头" ); } private void eat (String name) { System.out.println("狗吃" +name); } public static void inAddr () { System.out.println("在吉山区有一只单身狗!" ); } public String getName () { return name; } public void setName (String name) { this .name = 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 public class MethodDemo01 { @Test public void getDeclaredMethods () { Class c = Dog.class ; Method[] methods = c.getDeclaredMethods(); for (Method method : methods) { System.out.println(method.getName()+"====>" + method.getParameterCount()+"===>" + method.getReturnType()); } } @Test public void getDeclardMethod () throws Exception { Class c = Dog.class; Method run = c.getDeclaredMethod("run" ); Dog jinMao = new Dog (); Object rs = run.invoke(jinMao); System.out.println(rs); Method eat = c.getDeclaredMethod("eat" ,String.class); eat.setAccessible(true ); Object rs1 = eat.invoke(jinMao,"肉" ); System.out.println(rs1); } }
暴力攻击集合泛型
反射可以破坏面向对象的封装性(暴力反射)
同时可以破坏泛型的约束性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 List<Double> scores = new ArrayList <>(); scores.add(99.3 ); scores.add(199.3 ); scores.add(89.5 );Class c = scores.getClass();Method add = c.getDeclaredMethod("add" , Object.class); add.invoke(scores,"波仔" ); System.out.println(scores);
反射作用
可以在运行时得到一个类的全部成分然后操作。
可以破坏封装性。
也可以破坏泛型的约束性。
更重要的用途是适合:做Java高级框架,基本上主流框架都会基于反射设计一些通用技术功能。
Mybatis框架:
你给任何一个对象数据我都可以直接帮你解析字段并且把对应数据保存起来。
Student (注册,把信息字段都存储起来)
Teacher (注册,把信息字段都存储起来)
Manager (注册,把信息字段都存储起来)
我现在用反射技术开发一个框架实现:
任何对象只要给我,我就可以把信息和字段都解析并存储起来。
反射适合做通用技术框架的底层实现,在框架的底层源码中我们经常看到反射的影子!!
第三章 注解 注解的概念
用在类上,方法上,成员变量,构造器,…上对成分进行编译约束,标记等操作的。
注解是JDK1.5的新特性。
注解相当一种标记,是类的组成部分,可以给类携带一些额外的信息。
注解是给编译器或JVM看的,编译器或JVM可以根据注解来完成对应的功能。
1 2 3 4 5 6 7 8 public class AnnotationDemo01 { }@FunctionalInterface interface A { void test () ; }
自定义注解 自定义注解的格式:
自定义注解用@interface关键字。
使用注解的格式:@注解名称。
注解默认可以标记很多地方。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Book @MyTest public class MyBook { @Book @MyTest private MyBook () { } @Book @MyTest public static void main (@MyTest String[] args) { @MyTest @Book int age = 12 ; } }@interface Book{ }@interface MyTest{ }
注解的属性 属性的格式
格式1:数据类型 属性名();
格式2:数据类型 属性名() default 默认值;
属性适用的数据类型:
八种基本数据类型(int, short, long, double, byte, char, boolean, float)
String,Class
以上类型的数组形式都支持
注解可以有属性,属性名必须带()
在用注解的时候,属性必须赋值,除非这个属性有默认值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @MyBook(name="《精通Java基础》",authors = {"播仔","Dlei","播妞"} , price = 99.9 ) public class AnnotationDemo01 { @MyBook(name="《精通MySQL数据库入门到删库跑路》",authors = {"小白","小黑"} , price = 19.9 , address = "北京") public static void main (String[] args) { } }@interface MyBook{ String name () ; String[] authors(); double price () ; String address () default "广州" ; }
注解的特殊属性-value
如果只有一个value属性的情况下,使用value属性的时候可以省略value名称不写
但是如果有多个属性,且多个属性没有默认值,那么value是不能省略的。
1 2 3 4 5 6 7 8 9 10 11 12 public class AnnotationDemo01 { }@interface Book{ String value () ; int age () default 10 ; }
元注解
元注解是sun公司提供的。
元注解是用在自定义注解上的注解。
元注解是用来注解自定义注解的。
元注解有两个:
@Target
:约束自定义注解只能在哪些地方使用,但是默认的注解可以在类,方法,构造器,成员变量,… 使用。
@Retention
:声明注解的生命周期 申明注解的作用范围:编译时,运行时。
@Target
作用:用来标识注解使用的位置,如果没有使用该注解标识,则自定义的注解可以使用在任意位置。
可使用的值定义在ElementType枚举类中,常用值如下
TYPE,类,接口
FIELD, 成员变量
METHOD, 成员方法
PARAMETER, 方法参数
CONSTRUCTOR, 构造器
LOCAL_VARIABLE, 局部变量
@Retention
作用:用来标识注解的生命周期(有效存活范围)
可使用的值定义在RetentionPolicy枚举类中,常用值如下
SOURCE:注解只作用在源码阶段,生成的字节码文件中不存在
CLASS:注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值.
RUNTIME:注解作用在源码阶段,字节码文件阶段,运行阶段(开发常用)
@Target
约束自定义注解可以标记的范围。
@Retention
用来约束自定义注解的存活范围。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class AnnotationDemo01 { private String name; @MyTest public static void main ( String[] args) { } @MyTest public void testRun () { } }@Target(ElementType.METHOD ) @Retention(RetentionPolicy.RUNTIME) @interface MyTest{ }
注解解析 我们会使用注解注释一个类的成分,那么就设计到要解析出这些注解的数据。开发中经常要知道一个类的成分上面到底有哪些注解,注解有哪些属性数据,这都需要进行注解的解析。
与注解解析相关的接口 1. Annotation
: 注解类型,该类是所有注解的父类。注解都是一个Annotation
的对象 1. AnnotatedElement
:该接口定义了与注解解析相关的方法 2所有的类成分Class, Method , Field , Constructor:都实现了AnnotatedElement
接口,他们都拥有解析注解的能力: * Annotation[] getDeclaredAnnotations()
获得当前对象上使用的所有注解,返回注解数组。 * T getDeclaredAnnotation(Class<T> annotationClass)
根据注解类型获得对应注解对象 * boolean isAnnotationPresent(Class<Annotation> annotationClass)
判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false
解析注解数据的原理
注解在哪个成分上,我们就先拿哪个成分对象。
比如注解作用成员方法,则要获得该成员方法对应的Method对象,再来拿上面的注解
比如注解作用在类上,则要该类的Class对象,再来拿上面的注解
比如注解作用在成员变量上,则要获得该成员变量对应的Field对象,再来拿上面的注解
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 AnnotationDemo01 { @Test public void parseClass () { Class c = BookStore.class ; if (c.isAnnotationPresent(Book.class)){ Book book = (Book) c.getDeclaredAnnotation(Book.class); System.out.println(book.value()); System.out.println(book.price()); System.out.println(Arrays.toString(book.authors())); } } @Test public void parseMethod () throws Exception { Class c = BookStore.class ; Method run = c.getDeclaredMethod("run" ); if (run.isAnnotationPresent(Book.class)){ Book book = (Book) run.getDeclaredAnnotation(Book.class); System.out.println(book.value()); System.out.println(book.price()); System.out.println(Arrays.toString(book.authors())); } } }@Book(value = "《Java基础到精通》" , price = 99.5 , authors = {"波仔","波妞"}) class BookStore { @Book(value = "《Mybatis持久层框架》" , price = 199.5 , authors = {"dlei","播客"}) public void run () { } }@Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @interface Book{ String value () ; double price () default 100 ; String[] authors(); }
注解模拟Junit框架 需求:定义若干个方法,只要加了MyTest注解,就可以被自动触发执行。
分析:
定义一个自定义注解MyTest. 只能注解方法。 存活范围一直都在。
定义若干个方法,只要有@MyTest
注解的方法就能被触发执行!! 没有这个注解的方法不能执行
注解和反射可以配合解决一些框架思想
注解可以实现标记的成分做特殊处理!!
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 public class TestDemo { @MyTest public void test01 () { System.out.println("===test01===" ); } public void test02 () { System.out.println("===test02===" ); } @MyTest public void test03 () { System.out.println("===test03===" ); } @MyTest public void test04 () { System.out.println("===test04===" ); } public static void main (String[] args) throws Exception { TestDemo t = new TestDemo (); Class c = TestDemo.class; Method[] methods = c.getDeclaredMethods(); for (Method method : methods) { if (method.isAnnotationPresent(MyTest.class)){ method.invoke(t); } } } }@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @interface MyTest{ }
第四章 动态代理
代理就是被代理者没有能力或者不愿意去完成某件事情,需要找个人代替自己去完成这件事。
动态代理只能为实现接口的实现类对象做代理(也可以只为接口做代理对象)
引入:
在业务开发中经常存在很多重复的方法代码,他们前后的代码形式是一样的
只有中间部分代码有差别!!这种时候代码冗余读很高
有没有一种方法可以直接省略前后重复的代码就可以完成功能,这时候用动态代理。
开发步骤:
必须有接口。
实现类要实现接口,定义自己的业务功能代码。
为业务功能做代理对象(动态代理,难点)
动态代理非常的灵活,可以为任意的接口实现类对象做代理
动态代理可以为被代理对象的所有接口的所有方法做代理,
动态代理可以在不改变方法源码的情况下,实现对方法功能的增强,
动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。
动态代理同时也提高了开发效率。
缺点:只能针对接口或者接口的实现类对象做代理对象,普通类是不能做代理对象的。
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 public class ProxyUtil { public static <T> T getProxy (Object obj) { return (T) Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler () { @Override public Object invoke (Object proxy, Method method, Object[] params) throws Throwable { long startTime = System.currentTimeMillis(); Object rs = method.invoke(obj,params); long endTime = System.currentTimeMillis(); System.out.println(method.getName()+"方法耗时:" +(endTime - startTime)/1000.0 +"s" ); return rs; } }); } }
day13-XML和Dom4j、装饰模式、工厂模式、commons-io工具包 第一章 Dom4j Dom4j获取Document对象和根元素 dom4j属于第三方技术,必须导入该框架!!
dom4j安装步骤:
去dom4j官网下载dom4j的框架:都是一些jar包。
把dom4j的核心jar包导入到当前项目中去。
在项目中创建一个文件夹:lib
将dom4j-2.1.1.jar文件复制到 lib 文件夹
在jar文件上点右键,选择 Add as Library -> 点击OK
在类中导包使用
Java提供了Class下的一个方法:
public InputStream getResourceAsStream(String path)
用于加载文件成为一个字节输入流返回!!
Document文档:Element getRootElement()
:获取根元素。
先导入dom4j框架
创建一个dom4j的解析对象:SAXReader
通过解析对象把xml文件解析成Document文档对象。
从Document文档对象中获取我们想要的xml信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 SAXReader saxReader = new SAXReader ();InputStream is = Dom4JDemo01.class.getResourceAsStream("/books.xml" );Document document = saxReader.read(is); System.out.println(document);Element root = document.getRootElement(); System.out.println(root.getName());
Element元素的API:
String getName()
:取元素的名称。
List<Element> elements()
:获取当前元素下的全部子元素(一级)
List<Element> elements(String name)
:获取当前元素下的指定名称的全部子元素(一级)
Element element(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 SAXReader saxReader = new SAXReader ();Document document = saxReader.read(new File ("Day13Demo/src/books.xml" ));Element root = document.getRootElement(); System.out.println(root.getName()); List<Element> sonElements = root.elements();for (Element sonElement : sonElements) { System.out.println(sonElement.getName()); } System.out.println("-------------------" ); List<Element> sonElements1 = root.elements("book" );for (Element sonElement : sonElements1) { System.out.println(sonElement.getName()); } System.out.println("-------------------" );Element son = root.element("user" ); System.out.println(son.getName());Element son1 = root.element("book" ); System.out.println(son1.attributeValue("id" ));
Dom4j获取属性信息 Element元素的API:
List<Attribute> attributes()
: 获取元素的全部属性对象。
Attribute attribute(String name)
:根据名称获取某个元素的属性对象。
String attributeValue(String var1)
:直接获取某个元素的某个属性名称的值。
Attribute对象的API:
String getName()
: 获取属性名称。
String getValue()
: 获取属性值。
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 SAXReader saxReader = new SAXReader ();Document document = saxReader.read(new File ("day13/src/books.xml" ));Element root = document.getRootElement();Element bookEle = root.element("book" ); List<Attribute> attributes = bookEle.attributes();for (Attribute attribute : attributes) { System.out.println(attribute.getName()+"=>" +attribute.getValue()); }Attribute descAttr = bookEle.attribute("desc" ); System.out.println(descAttr.getName()+"--->" +descAttr.getValue()); System.out.println(bookEle.attributeValue("id" )); System.out.println(bookEle.attributeValue("desc" ));
Dom4j获取XML文本
String elementText(String name)
: 可以直接获取当前元素的子元素的文本内容
String elementTextTrim(String name)
: 去前后空格,直接获取当前元素的子元素的文本内容
String getText()
:直接获取当前元素的文本内容。
String getTextTrim()
:去前后空格,直接获取当前元素的文本内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 SAXReader saxReader = new SAXReader ();Document document = saxReader.read(new File ("Day13Demo/src/books.xml" ));Element root = document.getRootElement();Element bookEle = root.element("book" ); System.out.println(bookEle.elementText("name" )); System.out.println(bookEle.elementTextTrim("name" )); System.out.println(bookEle.elementText("author" )); System.out.println(bookEle.elementTextTrim("author" )); System.out.println(bookEle.elementText("sale" )); System.out.println(bookEle.elementTextTrim("sale" )); Element bookNameEle = bookEle.element("name" ); System.out.println(bookNameEle.getText()); System.out.println(bookNameEle.getTextTrim());
Dom4j解析案例 Contacts.xml 解析成===> List<Contact>
首先定义一个Contact
类
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 Contact { private int id ; private boolean vip; private String name ; private char sex ; private String email ; public Contact () { } public Contact (int id, boolean vip, String name, char sex, String email) { this .id = id; this .vip = vip; this .name = name; this .sex = sex; this .email = email; } public int getId () { return id; } public void setId (int id) { this .id = id; } public boolean isVip () { return vip; } public void setVip (boolean vip) { this .vip = vip; } public String getName () { return name; } public void setName (String name) { this .name = name; } public char getSex () { return sex; } public void setSex (char sex) { this .sex = sex; } public String getEmail () { return email; } public void setEmail (String email) { this .email = email; } public String toString () { return "Contact{id = " + id + ", vip = " + vip + ", name = " + name + ", sex = " + sex + ", email = " + email + "}" ; } }
解析代码
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 SAXReader saxReader = new SAXReader ();Document document = saxReader.read(new File ("day13/src/Contacts.xml" ));Element root = document.getRootElement(); List<Element> sonElements = root.elements(); List<Contact> contactList = new ArrayList <>();if (sonElements != null && sonElements.size() > 0 ) { for (Element sonElement : sonElements) { Contact contact = new Contact (); contact.setId(Integer.valueOf(sonElement.attributeValue("id" ))); contact.setVip(Boolean.valueOf(sonElement.attributeValue("vip" ))); contact.setName(sonElement.elementText("name" )); contact.setSex(sonElement.elementText("gender" ).charAt(0 )); contact.setEmail(sonElement.elementText("email" )); contactList.add(contact); } } System.out.println(contactList);
Xpath表达式 用于检索XML中的某些信息
XPath使用步骤:
导入dom4j框架。(XPath依赖于Dom4j技术,必须先倒入dom4j框架!)
导入XPath独有的框架包。jaxen-1.1.2.jar
XPath常用API:
List<Node> selectNodes(String var1)
:检索出一批节点集合。
Node selectSingleNode(String var1)
:检索出一个节点返回。
XPath提供的四种检索数据的写法:
绝对路径
相对路径
全文搜索
属性查找
绝对路径: /根元素/子元素/子元素。
相对路径: ./子元素/子元素。 (.代表了当前元素)
全文搜索://元素
在全文找这个元素//元素1/元素2
在全文找元素1下面的一级元素2//元素1//元素2
在全文找元素1下面的全部元素2
属性查找。//@属性名称
在全文检索属性对象。//元素[@属性名称]
在全文检索包含该属性的元素对象。//元素[@属性名称=值]
在全文检索包含该属性的元素且属性值为该值的元素对象。
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 public class XPathDemo { @Test public void path01 () throws Exception { SAXReader saxReader = new SAXReader (); InputStream is = Dom4JDemo01.class.getResourceAsStream("/Contact.xml" ); Document document = saxReader.read(is); List<Node> nameNodes = document.selectNodes("/contactList/contact/name" ); for (Node nameNode : nameNodes) { System.out.println(nameNode.getText()); } } @Test public void path02 () throws Exception { SAXReader saxReader = new SAXReader (); InputStream is = Dom4JDemo01.class.getResourceAsStream("/Contact.xml" ); Document document = saxReader.read(is); Element root = document.getRootElement(); List<Node> nameNodes = root.selectNodes("./contact/name" ); for (Node nameNode : nameNodes) { System.out.println(nameNode.getText()); } } @Test public void path03 () throws Exception { SAXReader saxReader = new SAXReader (); InputStream is = Dom4JDemo01.class.getResourceAsStream("/Contact.xml" ); Document document = saxReader.read(is); List<Node> nameNodes = document.selectNodes("//name" ); for (Node nameNode : nameNodes) { System.out.println(nameNode.getText()); } System.out.println("-----------------" ); List<Node> nameNodes1 = document.selectNodes("//contact/name" ); for (Node nameNode : nameNodes1) { System.out.println(nameNode.getText()); } System.out.println("-----------------" ); List<Node> nameNodes2 = document.selectNodes("//contact//name" ); for (Node nameNode : nameNodes2) { System.out.println(nameNode.getText()); } } @Test public void path04 () throws Exception { SAXReader saxReader = new SAXReader (); InputStream is = Dom4JDemo01.class.getResourceAsStream("/Contact.xml" ); Document document = saxReader.read(is); List<Node> attributs = document.selectNodes("//@id" ); for (Node attribut : attributs) { Attribute attr = (Attribute) attribut; System.out.println(attr.getName() + "--->" +attr.getValue()); } System.out.println("---------------" ); List<Node> nodeEles = document.selectNodes("//contact[@id]" ); for (Node nodeEle : nodeEles) { System.out.println(nodeEle.getName()); } System.out.println("---------------" ); Node nodeEle = document.selectSingleNode("//contact[@id=2]" ); Element ele = (Element) nodeEle; System.out.println(ele.elementTextTrim("name" )); } }
解析Mybatis的核心配置文件信息 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 ParseXMLConfig { @Test public void parseXML () throws Exception { SAXReader saxReader = new SAXReader (); Document document = saxReader.read(ParseXMLConfig.class.getResourceAsStream("/sqlMapConfig.xml" )); Element root = document.getRootElement(); Element environments = root.element("environments" ); Element environment = environments.element("environment" ); Element dataSource = environment.element("dataSource" ); List<Element> properties = dataSource.elements(); for (Element property : properties) { System.out.println(property.attributeValue("name" ) +"==>" +property.attributeValue("value" )); } } }
第二章 设计模式 工厂设计模型 什么是工厂设计模式?
工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一
这种类型的设计模式属于创建型模式,它提供了一种创建对象的方式
之前我们创建类对象时, 都是使用new
对象的形式创建, 除new
对象方式以外,工厂模式也可以创建对象
工厂设计模式的作用:
对象通过工厂的方法创建返回,工厂的方法可以为该对象进行加工和数据注入。
可以实现类与类之间的解耦操作
优点:工厂模式的存在可以改变创建对象的方式,解决类与类之间的耦合
缺点:工厂设计模式多了一个工厂类
FactoryPattern
1 2 3 4 5 6 7 8 public class FactoryPattern { public static Animal createAniaml () { return new Cat (); } }
创建对象可以使用
1 2 Animal a = FactoryPattern.createAniaml(); a.run();
之后如果需要修改创建的对象,可以直接在FactoryPattern
中修改对象创建
装饰设计模式 装饰模式指的是在不改变原类, 动态地扩展一个类的功能。
思想:是创建一个新类,包装原始类,从而在新类中提升原来类的功能!!
装饰模式可以在不改变原类的基础上对类中的方法进行扩展增强,实现原则为:
定义父类
定义原始类,继承父类,定义功能。
定义装饰类,继承父类,包装原始类,增强功能!!
InputStream
1 2 3 4 public abstract class InputStream { public abstract void read () ; public abstract void close () ; }
FileInputStream
1 2 3 4 5 6 7 8 9 10 11 12 public class FileInputStream extends InputStream { @Override public void read () { System.out.println("读取数据~~~" ); } @Override public void close () { System.out.println("关闭流~~~" ); } }
BufferedInputStream
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class BufferedInputStream extends InputStream { private InputStream is ; public BufferedInputStrem (InputStream is) { this .is = is; } @Override public void read () { System.out.println("开启高效缓冲读取~" ); is.read(); } @Override public void close () { is.close(); } }
Demo
1 2 3 InputStream is = new BufferedInputStrem (new FileInputStream ()); is.read(); is.close();
第三章 Commons-io包 什么是Commons-io
包?
commons-io是apache开源基金组织提供的一组有关IO操作的类库,可以挺提高IO功能开发的效率。commons-io工具包提供了很多有关io操作的类,见下表:
包
功能描述
org.apache.commons.io
有关Streams、Readers、Writers、Files的工具类
org.apache.commons.io.input
输入流相关的实现类,包含Reader和InputStream
org.apache.commons.io.output
输出流相关的实现类,包含Writer和OutputStream
org.apache.commons.io.serialization
序列化相关的类
步骤:
下载commons-io相关jar包;http://commons.apache.org/proper/commons-io/
把commons-io-2.6.jar包复制到指定的Module的lib目录中
将commons-io-2.6.jar加入到classpath中
IOUtils
和FileUtils
可以方便的复制文件和文件夹
1 2 3 4 5 6 7 8 9 10 IOUtils.copy(new FileInputStream ("Day13Demo/src/books.xml" ), new FileOutputStream ("Day13Demo/new.xml" )); FileUtils.copyFileToDirectory(new File ("Day13Demo/src/books.xml" ), new File ("D:/itcast" )); FileUtils.copyDirectoryToDirectory(new File ("D:\\itcast\\约吧图片服务器" ) , new File ("D:\\" )); Files.copy(Paths.get("Day13Demo/src/books.xml" ) , new FileOutputStream ("Day13Demo/new11.txt" ));
第四章 Base64 Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。
在Java 8中,Base64编码已经成为Java类库的标准。
Java 8 内置了 Base64 编码的编码器和解码器。
Base64工具类提供了一套静态方法获取下面三种BASE64编解码器:
基本: 输出被映射到一组字符A-Za-z0-9+/,编码不添加任何行标,输出的解码仅支持A-Za-z0-9+/。
URL: 输出映射到一组字符A-Za-z0-9+_,输出是URL和文件。
MIME: 输出隐射到MIME友好格式。输出每行不超过76字符,并且使用’\r’并跟随’\n’作为分割。编码输出最后没有行分割。
内嵌类
序号
内嵌类 & 描述
1
static class Base64.Decoder 该类实现一个解码器用于,使用 Base64 编码来解码字节数据。
2
static class Base64.Encoder 该类实现一个编码器,使用 Base64 编码来编码字节数据
方法
序号
方法名 & 描述
1
**static Base64.Decoder getDecoder()**返回一个 Base64.Decoder ,解码使用基本型 base64 编码方案。
2
**static Base64.Encoder getEncoder()**返回一个 Base64.Encoder ,编码使用基本型 base64 编码方案。
3
**static Base64.Decoder getMimeDecoder()**返回一个 Base64.Decoder ,解码使用 MIME 型 base64 编码方案。
4
**static Base64.Encoder getMimeEncoder()**返回一个 Base64.Encoder ,编码使用 MIME 型 base64 编码方案。
5
**static Base64.Encoder getMimeEncoder(int lineLength, byte[] lineSeparator)**返回一个 Base64.Encoder ,编码使用 MIME 型 base64 编码方案,可以通过参数指定每行的长度及行的分隔符。
6
**static Base64.Decoder getUrlDecoder()**返回一个 Base64.Decoder ,解码使用 URL 和文件名安全型 base64 编码方案。
7
**static Base64.Encoder getUrlEncoder()**返回一个 Base64.Encoder ,编码使用 URL 和文件名安全型 base64 编码方案。
注意: Base64 类的很多方法从 java.lang.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 try { String rs1 = Base64.getEncoder().encodeToString("黑马程序员" .getBytes()); System.out.println(rs1); byte [] buffer = Base64.getDecoder().decode(rs1); System.out.println(new String (buffer)); String rs2 = Base64.getUrlEncoder().encodeToString("?loginName=黑马&passWord=123456" .getBytes()); System.out.println(rs2); byte [] buffer2 = Base64.getUrlDecoder().decode(rs2); System.out.println(new String (buffer2)); StringBuilder sb = new StringBuilder (); for (int i = 0 ; i < 10 ; ++i) { sb.append(UUID.randomUUID().toString()); } String rs3 = Base64.getMimeEncoder().encodeToString(sb.toString().getBytes()); System.out.println(rs3); byte [] buffer3 = Base64.getMimeDecoder().decode(rs3); System.out.println(new String (buffer3)); }catch (Exception e){ System.out.println("Error :" + e.getMessage()); }