Java第五章
一、面向对象:static(静态)
static:叫静态,可以修饰成员变量、成员方法。
成员变量按照有无static修饰分为两种:
类变量(属于类):有static修饰,属于类,在计算机里只有一份,会被类的全部对象共
享。
实例变量(对象的变量):无static修饰,属于每个对象的。(只能通过对象访问)。
package com.mexin.staticdemo_1;
public class Test {
public static void main(String[] args) {
/*目标:掌握类变量和实例变量的用法,用学生类举例*/
//类变量的使用
Student.name = "Mexin,推荐使用这种方式调用"; //类变量,可以直接使用类名调用而且推荐这样写
System.out.println(Student.name);
//如果使用对象调用类变量,那么得创建一个学生类对象比如叫做 s1
Student s1 = new Student();
//创建完了类对象 s1 后再用 s1 调用类对象name
s1.name = "能调用,但不推荐这种";
System.out.println(s1.name);
//实例变量的调用,此调用前那就必须要有一个类对象才可以调用
//s2.age = 18; //s2 这个学生类对象并没有创建,上面只创建了 s1对象,所以会报错
s1.age = 22; //类对象s1调用实例变量age(对象变量)
System.out.println("年龄:" + s1.age);
/*
* 运行结果:
* Mexin,推荐使用这种方式调用
* 能调用,但不推荐这种
* 年龄:22 */
}
}
package com.mexin.staticdemo_1;
public class Student { //学生类:姓名,年龄等
//定义一个类变量姓名
static String name;
//定义一个实例变量(对象变量)
int age;
}
成员方法按照有无static方法修饰分为(与成员变量的用法一致,可以仿照上面举例即可):
类方法(属于类):有static修饰的方法
注意:类方法中就只能直接访问类成员(变量和方法),不可以直接访问实例成员
实例方法(属于对象):无static修饰的方法
注意:实例方法是可以直接访问类成员的(变量和方法都可),也可以访问实例成员。
可以出现this关键字
代码块
静态代码块和实例代码块
单例设计模式
确保一个类只有一个对象
写法:
把类的构造器私有
定义一个类变量记住类的一个对象
定义一个类方法返回对象
任务管理器就是单例模式
懒汉式单例
二、面向对象:继承
Java中提供了一个关键字extends,用这个关键字,可以让一个类和另一个类建立起父子关系
公开的东西子类全都可以用
继承的优点:
减少重复代码的编写
package com.mexin.extendsdemo_2;
//父类
public class People {
//所有人都有的
private String name;
//getter,setter方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.mexin.extendsdemo_2;
//子类
public class Teacher extends People{
//老师独有的
private String skill;
//getter,setter方法
public String getSkill() {
return skill;
}
public void setSkill(String skill) {
this.skill = skill;
}
public void showInfo(){
System.out.println("姓名:" + getName());
System.out.println("技能:" + skill);
}
}
package com.mexin.extendsdemo_2;
public class Test {
public static void main(String[] args) {
/*目标:继承的优点,减少重复代码的编写
* 创建一个父类People,里面包含的是所有子类都有的成员属性
* 创建一个子类Teacher
* 让子类继承父类*/
//创建一个老师对象t1
Teacher t1 = new Teacher();
//给对象存入数据
t1.setName("Mexin");
t1.setSkill("教学能力极强,善于表达");
//对象调用方法
t1.showInfo();
}
}
继承的相关注意事项
权限修饰符
public,private,protected,缺省
Java是单继承的,Java中的类不支持多继承,但是支持多层继承(A继承B,B继承C)。
Object类
我们写的所有类都是object的子类或者子孙类
方法重写
子类重写toString,目的是可以返回对象的内容
子类构造器
super()
this(...)调用兄弟构造器
三、面向对像:多态
对象多态:可以理解为一个事物有多个身份。比如一个人可以是爸爸,可以是儿子,也可以是丈
夫。
行为多态:同一个事情,不同事物表现不同。比如同样是跑步,老年人就跑的慢,年青人就跑的
快 。
解决办法:
多态也有强制转换
四、final关键字和常量
final:可以修饰类,方法,变量
final修饰类,则类不能被继承,为最终类,修饰方法,则该方法不能被重写,修饰变量,该变量只能被赋值一次
常量:使用了static final 修饰的成员变量就是常量
通常用于记录系统的配置信息
五、抽象类(abstract)
Java中有个关键字:abstract,可以修饰类、成员方法
abstract修饰类,该类就是抽象类,修饰成员方法,该方法就是抽象方法
抽象类:不一定有抽象方法,有抽象方法就一定有抽像类
类有的成员(成员变量、方法、构造器),抽像类都有
使用抽象类的好处:
分析:创建一个抽像父类,把方法写成抽像方法,定义两个子类,继承父类
package com.mexin.abstractdemo;
public abstract class Animal {
//定义名字
private String name;
public abstract void cry();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.mexin.abstractdemo;
public class Cat extends Animal {
//快捷键:Alt + Enter
@Override
public void cry() {
System.out.println(getName() + "小猫喵喵叫~~");
}
}
package com.mexin.abstractdemo;
public class Dog extends Animal{
@Override
public void cry() {
System.out.println(getName() + "小狗汪汪叫~~");
}
}
package com.mexin.abstractdemo;
public class Test {
public static void main(String[] args) {
//创建对象新方法
Animal c = new Cat();
c.setName("汤姆");
c.cry();
Animal d = new Dog();
d.setName("吉姆");
d.cry();
//创建对像,老方法
// Cat cat = new Cat();
// Dog dog = new Dog();
//
// cat.setName("小猫");
// dog.setName("小狗");
// cat.cry();
// dog.cry();
}
}
六、接口(interface)
A就是定义的接口 ,里面只能有成员变量和成员方法,并且成员变量必须是一个常量,成员方法默认是抽象方法
使用接口的优点:
解释:
七、内部类
public class Outer{ //外部类
public class Inner{ //内部类
//可以有成员变量等
}
}
静态内部类
使用static修饰的内部类
匿名内部类
枚举类
是一种特殊的类
注意:
枚举类中的第一行,只能写一些合法的标识符(名称),多个名称用逗号隔开。
这些名称,本质上是常量,每个常量都会记住枚举类的一个对象。
枚举的应用
用来表示一组信息,然后作为参数进行传输。
做信息标志和分类
泛型
定义类、接口、方法时,同时声明了一个或者多个类型变量(如<E>),称为泛型类、泛型接口,泛型方法,他们统称为泛型。
泛型类
泛型接口
泛型方法
通配符<?>,在使用泛型的时候可以代表一切类型
<? extends 类名>,上限,包括类名及其子类
<? super 类名>,下限,包括类名及其父类
泛型的注意事项:
常用API
Object
用idea重写equals方法(比较两对象的内容),直接输入eq,就会弹出提示,然后一直回车回车
objects类:
是一个工具类,提供了很多操作对象的静态方法。
包装类:
把基本类型的数据包装成对象
StringBuilder:
代表可变字符串对象,相当于一个容器,它里面装的字符串是可以改变的,就是用来操作字符串的。
StringBuilder比String更适合做字符串的修改操作,效率会更高,代码也会更简洁
StringBuilder支持链式编程
StringJoiner
用来操作字符串,可以看成是一个容器,创建之后里面的内容是可变的
不仅能提高字符串的操作效率,并且在有些场景下使用它操作字符串,代码会更简洁
Math类
System类
代表程序所在的系统,也是一个工具类
Runtime
程序所在的运行环境,是一个单例类
BigDecimal
用于解决浮点型运算时,出现结果失真的问题
Date
SimpleDateFormat简单日期格式化
calendar:代表的是系统此时对应的日历
通过它可以单独获取、修改时间的年、月、日、时、分、秒等
现在学习新增的
ZoneId
ZonedDateTime
instant,时间线上的某个时刻,时间戳,做代码的性能分析,或者记录用户的最后操作时间
DateTimeFormatter
用于时间的格式化和解析
Period:计算日期间隔 (年、月、日)
可以用于计算两个LocalDate对象相差的年数、月数、天数
Duration:持续时间,计算时间间隔(时、分、秒、纳秒)
Arrays
用来操作数组的工具类
上述有小数点计算的结果要考虑数据失真问题,需要使用BigDecimal来解决
数组中存储对象,如何排顺序?
改进
方式二:
Lambda表达式
只能用于简化函数式接口的匿名内部类的代码写法
函数式接口:有且只有一个抽象方法的接口。一般都会有一个“@FunctionalInterface”的注解
格式
方法引用(::)
静态方法的引用:
类名::静态方法
如果某个Lambda表达式里只是调用一个静态方法,并且前后参数的形式一致,就可以使
用静态方法引用
实例方法的引用:
对象名::实例方法
如果某个Lambda表达式里只是调用一个实例方法,并且前后参数的形式一致,就可以使
用实例方法引用
特定类型的方法引用:
类型::方法
如果某个Lambda表达式里只是调用一个实例方法,并且前面参数列表中的第一个参数是
作为方法的主调,后面的所有参数都是作为该实例方法的入参的,则此时就可以使用特
定类型的方法引用
构造器引用:
类名::new
如果某个Lambda表达式里只是在创建对象,并且前后参数情况一致,就可以使用构造器
引用
八、常见算法
1.排序算法
冒泡排序:每次都是找出最大值放到后面
选择排序:每轮选择当前位置,开始找出后面的较小值于该位置交换
优化:
2.查找算法
二分查找:Arrays.binarySearch(数组名,要找的值)【此方法是Java的自带二分查找方法】
九、正则表达式
用来校验数据格式是否合法
在一段文本中查找满足要求的内容
[1-9] 代表的是第一位要是1-9之间的数字
\\d{5,19}代表的是全为数字,长度为5位-19位
正则表达式的书写规则
校验电话号码
校验邮箱
查找要求的文本内容
十、异常
处理编译时异常
自定义异常
异常的处理
1.底层异常都往外抛,到顶层集中处理
2. 捕获异常后,尝试修复
Java第六章
一、集合进阶(一)
集合体系结构
单列集合(Collection),双列集合(Map)
Collection
是一个接口,支持泛型
Collection提供的常用方法
拓展方法addall()
Collection的遍历方式
1.迭代器
2.增强for循环
3.Lambda表达式
List
遍历方式
for循环
迭代器
增强for循环
lambda表达式
Linkedlist完成队列(先进先出)
Linkedlist完成栈(先进后出) 压栈(push),出栈(pop)
Set集合
哈希值
int类型的数值,Java中每一个对象都有一个哈希值
LinkedHashSet
TreeSet(可排序)
集合的并发修改异常
二、集合的进阶(二)
可变参数
案例:斗地主
package com.mexin.doudizhugame;
/*
* 目标斗地主游戏开发
* 1.一副牌总共有54张牌
* 2.点数有:"3","4","5","6","7","8","9","10","J","Q","K","A","2"
* 3.花色:"♠","♦","♥","♣"
* 4.大小王:"🃏(小王)""🤴(大王)"
* 5.每个点数分别需要组合4种花色
* 6.开始游戏时,需要发51张牌给玩家,留3张作为底牌给地主
*
* 分析:
* 1.需要有一副新牌
* 2.需要有一个房间对象,进行游戏
* 3.将牌的顺序要打乱,再发给玩家
* 4.给玩家手里的牌进行升序或者降序排序
*
* 实现:
* 1.设计一个牌类Card,用来实现54牌
* 2.创建一个房间类Room,进行斗地主*/
public class GameDemo {
public static void main(String[] args) {
//创建一个房间,里面有一副新牌
Room r1 = new Room();
//开始打乱牌的顺序
r1.start();
}
}
package com.mexin.doudizhugame;
public class Card {
//1.编写成员变量
//一张牌上面要有点数,以及花色
private String num; //点数
private String color;//花色
//每张牌有大小区分
private int size;//用来区分牌的大小
//4.提供无参构造
public Card() {
}
//3.提供有参构造
public Card(String num, String color, int size) {
this.num = num;
this.color = color;
this.size = size;
}
//2.提供getter,setter方法
public String getNum() {
return num;
}
public void setNum(String num) {
this.num = num;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
//重写toString方法
@Override
public String toString() {
return color + num;
}
}
package com.mexin.doudizhugame;
import java.util.*;
public class Room {
//房间里面需要有一副新牌
//创建一个list集合用来装54张牌,代表一副新牌
private List<Card> allCards = new ArrayList<>();
//提供一个无参构造器,在里面创造54张牌
public Room(){
//用来产生54张牌,存入allCards集合中去
//点数的个数是确定的,类型也确定,所以定义一个String类型的静态数组
String[] nums = {"3","4","5","6","7","8","9","10","J","Q","K","A","2"};
//同样的花色也是个数确定,类型确定,也是定义静态数组
String[] colors = {"♠","♦","♥","♣"};
//定义一个变量size,来充当牌的大小比较
int size = 0;
//开始让点数与花色匹配,一个点数对应四个花色,先遍历点数再遍历花色
for (String num : nums) {
size++;
for (String color : colors) {
//创建一个牌对象,并封装
Card c = new Card(num,color,size);
//将得到的牌存入allCards中,注意这里只有52张牌,还剩大小王
allCards.add(c);
}
}
//单独存入大小王,注意size,先加再存
Card c1 = new Card("","🃏",++size);
Card c2 = new Card("","🤴",++size);
//采用addAll()方法将大小王直接存入集合中去
Collections.addAll(allCards,c1,c2);
//打印出这副新牌,这里需要在Card类中重写toString方法才可以打印出内容,不然得到的是地址
// System.out.println("新牌:"+allCards);
}
/*
* 斗地主启动方法
* */
public void start() {
//开始洗牌:allCards,Collection中自带一个shuffle方法,就是打乱顺序的
Collections.shuffle(allCards);
//打印来看看是否洗好了牌
// System.out.println("洗牌后:" + allCards);
//洗好牌之后,开始发牌
/*
* 因为需要三个玩家参与,然后每个玩家无非就是17,18张牌,
* 可以定义三个集合代表三名玩家,用集合来装发的牌
* 这里采用ArrayList集合,原因是支持排序,允许重复的牌,支持索引*/
List<Card> zhongli = new ArrayList<>();
List<Card> naxida = new ArrayList<>();
List<Card> leishen = new ArrayList<>();
//开始发牌,依次发出51张牌,剩余三张牌作为底牌,最后展示并发给地主
/*
* allCard[♣ A, ♠ 4, ♦ 8, ♥ A, ♣ 7, ♣ 5, ♠ K, ♦ 2,....
* 索引值 0 1 2 3 ....
* 依据索引值可以看出,对3取余,结果为0就是第一个人,1就是第二个人,2就是第三个人*/
//for循环遍历牌,然后对3取余
for (int i = 0; i < allCards.size() - 3; i++) {//注意留三张底牌
Card c = allCards.get(i);
if(i % 3 == 0){
//zhongli街牌
zhongli.add(c);
}else if(i % 3 == 1){
//naxida接牌
naxida.add(c);
}else if(i % 3 == 2){
//leishen接牌
leishen.add(c);
}
}
//查看发完的牌
System.out.println("钟离:" + zhongli);
System.out.println("纳西达:" + naxida);
System.out.println("雷神:" + leishen);
//开始取最后三张牌作为底牌
/*
* 官方提供了一个集合的元素截取方法subList()方法
* 定义一个list集合lastThreCard存储最后三张底牌*/
List<Card> lastThreCard = allCards.subList(allCards.size() - 3 ,allCards.size());
//开始抢地主
/*
* 可以利用Random随机产生一个数,对3取余,结果为0就是第一个人地主,1就是第二个人,2就是第三个人*/
//展示底牌
System.out.println("底牌:"+lastThreCard);
//将底牌给地主,这里先假设钟离抢到地主
zhongli.addAll(lastThreCard);
//查看地主的所有牌
// System.out.println("添加了地主牌之后:"+zhongli);
//发完牌之后,开始排序
sortCard(zhongli);
sortCard(naxida);
sortCard(leishen);
//看牌
System.out.println("钟离的牌排好序后:" + zhongli);
System.out.println("纳西达的牌排好序后:" + naxida);
System.out.println("雷神的牌排好序后:" + leishen);
}
/*
* 对牌进行排序*/
private void sortCard(List<Card> cards) {
//利用比较器进行两两比较排序
Collections.sort(cards, new Comparator<Card>() {
@Override
public int compare(Card o1, Card o2) {
return o1.getSize() - o2.getSize(); //升序排序,要降序直接反过来即可
}
});
}
}
Map集合
需要存储一 一对应的数据时,考虑用Map集合
Map集合常用方法
Map集合的遍历方式
Lambda遍历(重点)
案例:统计投票的人数
package com.mexin.mapdemo;
/*
* 需求:
* 某个班级80名学生,现在需要组织秋游活动,班长提供了四个景点,分别是A、B、C、D,
* 每个学生只能选择一个景点,请统计出最终那个景点想去的人数最多
*
* 分析:
* 1.需要有80个选择的景点数据
* 2.准备一个map集合用于存储统计出来的结果
* 3.遍历80个人选择的景点,记录每个景点选择的人数
* */
import java.util.*;
public class StatisticsDemo {
public static void main(String[] args) {
//1.定义一个list集合,用于存储80个人选择好的景点
List<String> sence = new ArrayList<>();
//2.准备一个数组,用于装入模拟的可选择的四个景点
String[] select = {"A","B","C","D"};
//4.创建一个随机数
Random r = new Random();
//3.用for循环,循环80次,模拟选择的景点结果
for (int i = 1; i <=80 ; i++) {
//利用随机数,产生0.1.2.3四个数据来进行景点的选择、
int index= r.nextInt(4);//边界值4,只会产生0-3的四个随机数
//将选择好的一个景点装入list集合sence里面
sence.add(select[index]);
}
//此时集合sence有80个选择好的景点结果
/*
* 现在开始遍历这些结果,统计各个景点选择的人数*/
//1.定义一个map集合,存储统计的结果
Map<String,Integer> statiscs = new HashMap<>();
//2.开始遍历80个景点数据,统计好
for (String s : sence) {//增强for循环来遍历
//需要先知道statistics里面是否存在某一个景点,如果存在则加一,如果不存在则存进集合,赋值为一
if (statiscs.containsKey(s)){
//进入到这里说明集合里面原先存在该景点,所以键值加一即可
statiscs.put(s,statiscs.get(s) + 1);
}else {
//说明集合里面没有该景点,所以要存入该景点,并且键值=1
statiscs.put(s,1);
}
}
//到这里统计结果就出来了
System.out.println(statiscs);
}
}
集合嵌套案例
public class QiantaoDemo {
public static void main(String[] args) {
//定义一个map集合来存储这些省份及城市信息
Map<String, List<String>> location = new HashMap<>();
//为list集合创建城市对象
List<String> cities1 = new ArrayList<>();
//将城市存储到cities里面
Collections.addAll(cities1,"南京市","扬州市","苏州市","无锡市","常州市");
//添加省份和城市信息到location中
location.put("江苏省",cities1);
List<String> cities2 = new ArrayList<>();
//将城市存储到cities里面
Collections.addAll(cities2,"武汉市","孝感市","十堰市","宜昌市","鄂州市");
//添加省份和城市信息到location中
location.put("湖北省",cities2);
List<String> cities3 = new ArrayList<>();
//将城市存储到cities里面
Collections.addAll(cities3,"石家庄市","唐山市","邢台市","保定市","张家口市");
//添加省份和城市信息到location中
location.put("河北省",cities3);
System.out.println(location);
//查询省份对应的城市,定义一个list集合cities来装查询道德城市
List<String> cities = location.get("湖北省");
for (String city : cities) {
System.out.println(city);
}
//整个map集合全部遍历,forEach加lambda表达式
location.forEach((p,c) ->{//p代表是取省份,c代表取城市
System.out.println(p + "-->" +c);
});
}
}
三、Stream
可以用于操作集合或者数组的数据
优势:Stream流大量的结合了lambda的语法风格来编程,提供了一种更加强大,更加简单的方式操作集合或者数组中的数据,代码更简洁,可读性更好。
Stream流的中间方法
注意:Stream只能收集一次
四、File,IO流
File
存储数据方式:
在内存中:变量、数组、对象、集合可以存储数据速度快,但是遇到断电,程序终止时数据会丢失
在硬盘中:利用文件存储,速度相对更慢,断电或者程序终止不会丢失
File是java.io.包下的类。File类的对象,用于代表当前操作系统的文件(可以是文件或者文件夹)
注意:File类只能对文件本身进行操作,不能读写文件里面存储的数据
创建File类的对象
五、递归
方法调用自身的形式称为方法递归
形式:
直接递归:方法自己调用自己
间接递归:方法调用其他方法,其他方法又回调方法自己
递归如果没有控制好终止,会出现递归死循环,导致栈内存溢出错误
案例:找到文件夹下的某个文件
六、字符集
七、IO流
用于读写数据的(可以读写文件,网络中的数据等)
文件字节输入流
文件字节输出流
以内存为基准,把内存中的数据以字节的形式写到文件中去
案例:文件复制
释放资源的方式
try-catch-finally
finally代码区的特点:无论try中的程序是否是正常执行了,还是出现了异常,最后都一定会执行finally区,除非jvm终止了。
try-with-resource(推荐使用)
将流定义在try()括号中,执行完流之后,系统会自动调用关闭流的方法,就不需要写finally了,简化代码了
注意:括号内只能放资源对象(比如流),资源都会实现一个AutoCloseable接口
文件字符输入流
文件字符输出流
注意:字符输出流写出数据后,必须刷新流,或者关闭流,写出去的数据才能生效
字节缓冲流
字符缓冲输入流
字符缓冲输出流
字符输入转换流
解决不同编码时,字符流读取文本内容乱码问题
字符输出转换流
可以控制写出去的字符使用什么字符集编码。
打印流
打印重定向
数据输出流
数据输入流
序列化流
io框架
在项目里面添加jar包
第一步:单击选择你的项目,鼠标右击选择目录,命名为lib
第二步:将要使用的jar包复制到lib中
第三步:单击选择lib,鼠标右击选择添加为库,即可。
package com.mexin.commons_io;
/*
* 目标:使用commonsio框架进行io的相关操作*/
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
public class CommonsIoTest {
public static void main(String[] args) throws Exception {
//使用commonsio下的FileUtils的copyFile()方法复制文件
FileUtils.copyFile(new File("io-app\\src\\mexin01.txt"),new File("io-app\\src\\mexin02.txt"));
//拷贝文件夹
FileUtils.copyDirectory(new File("E:\\Photo"),new File("C:\\Users\\50141\\Desktop\\simi"));
//删除非空文件夹
FileUtils.deleteDirectory(new File("C:\\Users\\50141\\Desktop\\simi"));
}
}
八、特殊文本文件和日志技术
特殊文本文件
1.内容是键值对
2.键不能重复
3.文件后缀一般是.properties
Properties
是一个Map集合(键值对集合),但是一般不当集合使用
核心作用:Properties是用来代表属性文件的,通过Properties可以读写属性文件里面的内容
user.properties文件的内容如下:
#以下是用户名和密码 admin=123456 唐老鸭=111 米老鼠=222 派大星=333
package com.mexin.properties;
import java.io.FileReader;
import java.util.Properties;
import java.util.Set;
/*
* 掌握使用Properties类读取属性文件中的键值对信息
* */
public class PropertiesTest {
public static void main(String[] args) throws Exception {
//创建一个Properties的对象叫ppt
Properties ppt = new Properties();
System.out.println(ppt);
//开始加载属性文件中的信息到ppt中
ppt.load(new FileReader("src\\user.properties"));
System.out.println(ppt);
//取出某一个键对应的值
System.out.println(ppt.getProperty("派大星"));
//遍历全部的键和值
//定义一个set集合存储,是无序的
Set<String> keys = ppt.stringPropertyNames();
for (String key : keys) {
String v = ppt.getProperty(key);
System.out.println(key + "=" + v);
}
System.out.println("====================================");
//用lambda表达式来遍历键和值
ppt.forEach((k,v) -> {
System.out.println(k + "=" + v);
});
}
}
package com.mexin.properties;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
/*
* 目标:掌握把键值对数据存入到属性文件中去
* */
public class PropertiesTest2 {
public static void main(String[] args) throws Exception {
//创建一个properties对象ppt
Properties ppt = new Properties();
//调用setProperty()方法存入数据到ppt
ppt.setProperty("海绵宝宝","666");
ppt.setProperty("章鱼哥","555");
ppt.setProperty("蟹老板","444");
ppt.setProperty("痞老板","777");
//调用store()方法把ppt里的数据写入到属性文件中
ppt.store(new FileWriter("src\\user2.properties"),"There are many admins and password");
}
}
XML
可扩展标记语言
在程序中读取xml文件(解析xml文件)
package com.mexin.xml;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
public class Dom4jTest {
public static void main(String[] args) throws Exception {
//创建一个SAXReader对象
SAXReader saxr = new SAXReader();
//利用saxr调用read()方法,解析xml为document放入doc
Document doc = saxr.read("src\\helloworld.xml");
//获得document的根元素对象
Element root = doc.getRootElement();
//打印根元素的投标签
System.out.println(root.getName());
}
}
将数据写入到xml中,采用io流写
日志技术
把程序运行的信息,记录到文件中,方便定位Bug,了解程序的执行请路况。
package com.mexin.log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.rmi.server.LogStream;
/*
* 目标:掌握LogBack日志框架的使用
* */
public class LogBackTest {
//创建一个日志对象
public static final Logger LOGGER = LoggerFactory.getLogger("LogBackTest");
public static void main(String[] args) {
try {
LOGGER.info("chu()方法开始执行~");
chu(10,2);
LOGGER.info("chu()方法执行完成~");
}catch (Exception e){
LOGGER.error("chu()方法执行失败了,出现了bug");
}
}
public static void chu(int a,int b){
//调用debug方法记录方法执行流程
LOGGER.debug("参数a:" + a);
LOGGER.debug("参数b:" + b);
int c = a / b;
LOGGER.info("方法执行完结果是" + c);//用日志记录结果就不需要输出语句了
}
}
核心配置文件logback.xml
LogBack设置日志的级别
九、多线程
线程(Thread):程序内部的一条执行流程。
单线程:程序中只有一条执行流程。
多线程:从软硬件上实现的多条执行流程技术(多条线程由cpu负责调度执行)
多线程创建方式一
package com.meixn.create_thread;
/*
* 1.让子类继承Thread线程类
* */
public class MyThread extends Thread{
//2.必须重写Thread类的run方法
@Override
public void run() {
//描述线程执行的任务
for (int i = 0; i < 5; i++) {
System.out.println("子线程t任务输出:" + i);
}
}
}
package com.meixn.create_thread;
/*
* 掌握线程的创建方法一:继承Thread类
* */
public class ThreadTest {
//main方法是由一条默认的主线程负责执行的
public static void main(String[] args) {
//3.创建一个MyThread线程类对象t,代表一个线程
Thread t = new MyThread();
//4.启动线程(自动执行重写的run方法的)
t.start();
for (int i = 0; i < 5; i++) {
System.out.println("主线程main任务输出:" + i + "a");
}
}
}
方法一优缺点
1.编码简单。
2.线程类已经集成Thread,无法继承其他类,不利于功能扩展。
注意事项
多线程创建方式二
package com.meixn.create_thread;
/*
* 1.定义一个任务类,实现Runnable接口
* */
public class MyRunnable implements Runnable{
//2.重写run方法
@Override
public void run() {
//子线程任务输出
for (int i = 0; i < 5; i++) {
System.out.println("子线程任务输出:" + i);
}
}
}
package com.meixn.create_thread;
/*
* 掌握多线程的创建方法二:实现runnable接口
* */
public class ThreadTest2 {
public static void main(String[] args) {
//3.创建一个任务对象target
Runnable target = new MyRunnable();
//把任务对象转换为线程对象
Thread t = new Thread(target);
//线程对象调用start方法
t.start();
//主线程任务输出
for (int i = 0; i < 5; i++) {
System.out.println("主线程任务输出:" + i + "a");
}
}
}
方式二的优缺点
1.任务类只是实现接口,可以继续继承其他类,实现其他接口,扩展性强。
2.需要多实现一个Runnable对象。
多线程创建方式三
package com.meixn.create_thread;
import java.util.concurrent.Callable;
/*
* 1.定义一个类,实现Callable接口
* */
public class MyCallable implements Callable {
//定义一个成员变量n
private int n;
//有参构造器,接收n的值
public MyCallable(int n) {
this.n = n;
}
//重写call方法
@Override
public Object call() throws Exception {
//子线程任务完成求1-n的和,返回线程执行的结果
int sum = 0;
for (int i = 1; i <= n; i++) {
sum = sum + i;
}
return "计算1到" + n + "的和是" + sum;
}
}
package com.meixn.create_thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
/*
* 掌握创建线程的方法三:实现Callable接口
* */
public class ThreadTest3 {
public static void main(String[] args) throws Exception {
//创建一个Callable对象
Callable<String> call = new MyCallable(100);
//将call封装为FutureTask对象
//FutureTask的作用
//1.是一个任务对象,实现了Runnable对象
//2.可以在线程执行完成之后,用未来任务对象调用get方法获取线程执行完毕的结果
FutureTask<String> f = new FutureTask<>(call);
//将f 交给Thread对象t
Thread t = new Thread(f);
//启动子线程
t.start();
//获取线程执行完毕的结果
//系统担心可能求和的方法会报错,所以会出现异常
//注意:如果代码执行到这里时,求和的方法还没执行完成,
//那么此时会暂停等待求和完毕才会获取结果
String s = f.get();
System.out.println(s);
}
}
优缺点:
1.线程任务类只是实现接口,可以继续继承类和实现接口,扩展性更强,可以在线程执行完毕后去获取线程执行后的结果
2.编码复杂一点
为线程设置名字
线程安全
多个线程同时访问一个共享资源,且存在修改资源
取钱案例(线程安全问题)
两人共用一个账户,余额5000,如果两人同时来取钱,并且各自都在取5000元,可能会出现什么问题?
线程安全问题出现的原因
1.同时存在多个线程执行
2.同时访问一个共享资源
3.存在修改该共享资源
程序模拟线程安全问题
package com.meixn.getmoney_thread;
/*
* 模拟线程安全问题
* */
public class ThreadTest {
public static void main(String[] args) {
//创建一个账户对象,代表两个人的共享账户
Account acc = new Account("ICBC-110",5000);
//创建两个线程,代表两个人,再去同一个账户中取钱
new GetMoneyThread(acc,"靓仔").start();
new GetMoneyThread(acc,"叼毛").start();
}
}
package com.meixn.getmoney_thread;
public class Account {
private String cardId;
private double money;
public Account() {
}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
两人同时过来取钱
public void drawMoney(double money) {
//先要知道是谁过来取钱
String name = Thread.currentThread().getName();
//判断余额是否足够
if (this.money >= money){
System.out.println(name + "取走了:" + money + "元,成功");
//更新余额
this.money = this.money - money;
System.out.println(name + "取钱后余额:" + this.money + "元");
}else{
System.out.println("余额不足,不能取钱");
}
}
}
package com.meixn.getmoney_thread;
public class GetMoneyThread extends Thread{//取钱的线程类,要继承Thread类,并且重写run方法
//定义一个Account类型的全变量
private Account account;
//有参构造器
public GetMoneyThread(Account acc,String name){
super(name);
this.account = acc;
}
@Override
public void run() {
//取钱
account.drawMoney(5000);
}
}
解决线程安全问题
采用线程同步方法
加锁:每次只允许一个线程加锁,加锁后才能进去访问,访问完毕后自动解锁,其他线程才能再加锁进来。
1.同步代码块的方式
//两人同时过来取钱
public void drawMoney(double money) {
//先要知道是谁过来取钱
String name = Thread.currentThread().getName();
//判断余额是否足够
synchronized (this) {//在实例方法中,最好选用this作为锁
if (this.money >= money){
System.out.println(name + "取走了:" + money + "元,成功");
//更新余额
this.money = this.money - money;
System.out.println(name + "取钱后余额:" + this.money + "元");
}else{
System.out.println("余额不足,不能取钱");
}
}
}
如果是静态方法,则使用类名.class作为锁
2.同步方法
注意:锁的范围更小性能是更好的。
3.Lock锁
package com.meixn.getmoney_thread;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Account {
private String cardId;
private double money;
//创建一个锁对象,final修饰,锁对象就不能被替换了,保护锁
private final Lock lk = new ReentrantLock();
public Account() {
}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
//两人同时过来取钱
public void drawMoney(double money) {
//先要知道是谁过来取钱
String name = Thread.currentThread().getName();
//加锁
lk.lock();
//判断余额是否足够
if (this.money >= money){
System.out.println(name + "取走了:" + money + "元,成功");
//更新余额
this.money = this.money - money;
System.out.println(name + "取钱后余额:" + this.money + "元");
}else{
System.out.println("余额不足,不能取钱");
}
//解锁
lk.unlock();
}
}
线程通信
线程池
可以复用线程的技术
线程池处理Runnable任务
package com.meixn.thread_pool;
public class MyRunnable implements Runnable{
@Override
public void run() {
//描述该任务需要做什么
System.out.println(Thread.currentThread().getName() + ":打Call了666~");
//让线程休眠一会,便于观察,sleep有异常,ALT + 回车键,抛异常
try {
Thread.sleep(3000);//休眠3s
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package com.meixn.thread_pool;
import java.util.concurrent.*;
public class ThreadPoolTest {
public static void main(String[] args) {
//通过ThreadPoolExecutor创建一个线程池对象pool
ExecutorService pool = new ThreadPoolExecutor(3,5,8,
TimeUnit.SECONDS,new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
//创建一个任务对象
Runnable target = new MyRunnable();
pool.execute(target); //线程池会自动创建一个新线程,自动处理这个任务
pool.execute(target); //线程池会自动创建一个新线程,自动处理这个任务
pool.execute(target); //线程池会自动创建一个新线程,自动处理这个任务
pool.execute(target); //进任务队列(前面的核心线程一直在忙时)
pool.execute(target); //进任务队列
pool.execute(target); //进任务队列
pool.execute(target); //进任务队列
//当核心线程都在忙,任务队列也满了,这个是时候还有任务,就会创建临时线程
pool.execute(target);//临时线程
pool.execute(target);//临时线程
//临时线程也满了之后,还有任务,就会触发拒绝
pool.execute(target);
//线程池任务执行完默认是不会结束运行的
pool.shutdown();//任务执行完毕,关闭线程池
//pool.shutdownNow();立即关闭线程池,不管任务是否执行完
}
}
线程池处理Callable任务
package com.meixn.thread_pool;
import java.util.concurrent.Callable;
/*
* 1.定义一个类,实现Callable接口
* */
public class MyCallable implements Callable {
//定义一个成员变量n
private int n;
//有参构造器,接收n的值
public MyCallable(int n) {
this.n = n;
}
//重写call方法
@Override
public Object call() throws Exception {
//子线程任务完成求1-n的和,返回线程执行的结果
int sum = 0;
for (int i = 1; i <= n; i++) {
sum = sum + i;
}
return Thread.currentThread().getName() + "计算1到" + n + "的和是" + sum;
}
}
package com.meixn.thread_pool;
import java.util.concurrent.*;
public class ThreadPoolTest2 {
public static void main(String[] args) throws Exception{
//通过ThreadPoolExecutor创建一个线程池对象pool
ExecutorService pool = new ThreadPoolExecutor(3,5,8,
TimeUnit.SECONDS,new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
//使用线程池处理Callable任务
Future<String> f1 = pool.submit(new MyCallable(100));//多态写法
Future<String> f2 = pool.submit(new MyCallable(200));//多态写法
Future<String> f3 = pool.submit(new MyCallable(300));//多态写法
Future<String> f4 = pool.submit(new MyCallable(400));//多态写法
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
System.out.println(f4.get());
pool.shutdown();
}
}
工具类Executors实现线程池
并发、并行
进程:正在运行的程序(软件)就是一个独立的进程
线程的生命周期
乐观锁
十、网络通信
网络通信三要素
IP地址
端口号
协议
UDP通信
多发多收
TCP通信
多发多收
TCP支持多个客户端同时通信
引入多线程
package com.mexin.tcp;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
public class ServerReaderThread extends Thread{
//定义成员变量socket
private Socket socket;
//提供有参构造器
public ServerReaderThread(Socket socket){
this.socket = socket;
}
//继承Thread重写run方法
@Override
public void run() {
try {//异常捕获
//定义字节输入流is
InputStream is = socket.getInputStream();
//将字节输入流包装成数据输入流
DataInputStream dis = new DataInputStream(is);
while (true){
try {
//开始接收客户端的消息
String msg = dis.readUTF();
//打印消息
System.out.println(msg);
} catch (Exception e) {
//提示客户端下线
System.out.println(socket.getRemoteSocketAddress() + "下线了");
dis.close();
socket.close();//释放资源
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.mexin.tcp;
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws Exception {
//1.创建Socket对象,并同时请求与服务器的连接
Socket socket = new Socket("127.0.0.1",9999);
//2.从Socket通信管道中得到一个字节输出流,用来发数据给服务端
OutputStream os = socket.getOutputStream();
//3.把低级的字节输出流包装成数据输出流
DataOutputStream dos = new DataOutputStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入需要发送的消息:");
String msg = sc.nextLine();
//判断用户是否退出
if("exit".equals(msg)){
System.out.println("本次会话已结束!");
dos.close();
socket.close();//释放资源
break;
}
//4.开始写数据出去
dos.writeUTF(msg);
//刷新
dos.flush();
}
}
}
package com.mexin.tcp;
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("=======服务端启动========");
//1.创建ServerSocket的对象,同时为服务端注册端口
ServerSocket serverSocket = new ServerSocket(9999);
while (true) {
//2.使用serverSocket对象,调用一个accept方法,等待客户的连接请求
Socket socket = serverSocket.accept();
//3.把这个通信管道交给一个独立的线程负责处理
new ServerReaderThread(socket).start();
}
}
}
综合案例
1.即时通信-群聊(学习端口转发思想)
package com.mexin.tcpdemo;
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws Exception {
//1.创建Socket对象,并同时请求与服务器的连接
Socket socket = new Socket("127.0.0.1",9999);
//创建一个独立的线程负责随机从socket中接收服务端发过来的消息
new ServerReaderThread(socket).start();
//2.从Socket通信管道中得到一个字节输出流,用来发数据给服务端
OutputStream os = socket.getOutputStream();
//3.把低级的字节输出流包装成数据输出流
DataOutputStream dos = new DataOutputStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入需要发送的消息:");
String msg = sc.nextLine();
//判断用户是否退出
if("exit".equals(msg)){
System.out.println("本次会话已结束!");
dos.close();
socket.close();//释放资源
break;
}
//4.开始写数据出去
dos.writeUTF(msg);
//刷新
dos.flush();
}
}
}
package com.mexin.tcpdemo;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class Server {
//定义一个集合存储
public static List<Socket> onLineSockets = new ArrayList<>();
public static void main(String[] args) throws Exception {
System.out.println("=======服务端启动========");
//1.创建ServerSocket的对象,同时为服务端注册端口
ServerSocket serverSocket = new ServerSocket(9999);
while (true) {
//2.使用serverSocket对象,调用一个accept方法,等待客户的连接请求
Socket socket = serverSocket.accept();
//把客户端的消息放入集合
onLineSockets.add(socket);
//3.把这个通信管道交给一个独立的线程负责处理
new ServerReaderThread(socket).start();
}
}
}
package com.mexin.tcpdemo;
import java.io.*;
import java.net.Socket;
public class ServerReaderThread extends Thread{
//定义成员变量socket
private Socket socket;
//提供有参构造器
public ServerReaderThread(Socket socket){
this.socket = socket;
}
//继承Thread重写run方法
@Override
public void run() {
try {//异常捕获
//定义字节输入流is
InputStream is = socket.getInputStream();
//将字节输入流包装成数据输入流
DataInputStream dis = new DataInputStream(is);
while (true){
try {
//开始接收客户端的消息
String msg = dis.readUTF();
//打印消息
System.out.println(msg);
//把收到的消息发给全部的客户端,包括自己
sendMsgToAll(msg);
} catch (Exception e) {
//提示客户端下线
System.out.println(socket.getRemoteSocketAddress() + "下线了");
Server.onLineSockets.remove(socket);
dis.close();
socket.close();//释放资源
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
//把收到的消息发给在线的客户端
private void sendMsgToAll(String msg) throws Exception {
for (Socket onLineSocket : Server.onLineSockets) {
OutputStream os = onLineSocket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF(msg);
dos.flush();
}
}
}
package com.mexin.tcpdemo;
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.Socket;
public class ClientReaderThread extends Thread{
//定义成员变量socket
private Socket socket;
//提供有参构造器
public ClientReaderThread(Socket socket){
this.socket = socket;
}
//继承Thread重写run方法
@Override
public void run() {
try {//异常捕获
//定义字节输入流is
InputStream is = socket.getInputStream();
//将字节输入流包装成数据输入流
DataInputStream dis = new DataInputStream(is);
while (true){
try {
//开始接收客户端的消息
String msg = dis.readUTF();
//打印消息
System.out.println(msg);
} catch (Exception e) {
//提示客户端下线
System.out.println(socket.getRemoteSocketAddress() + "下线了");
dis.close();
socket.close();//释放资源
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
十一、Java高级技术
单元测试
junit单元测试框架
反射
注解
元注解
修饰注解的注解