*
一. java 的基础重载与重写的区别
多个方法名称一样,但是参数的列表不一样
方法重载的注意事项,下列是与方法重载有关的
下列与方法重载无关 错误的方法重载
好处:只需要记住一个方法名称就可以实现类似的多个功能
sum(10, 26);
public static int sum(int a, int b) {
return a + b;
}
sum(112, 30.9);
public static int sum(int a, double b) {
return a + b;
}
sum(25.3, 10.68);
public static int sum(double a, double b) {
return int(a + b);
}
sum(12, 19, 21);
public static int sum(int a, int b, int c) {
return a + b + c;
}
动态的初始化(指定长度)
// 格式:数据类型[] 数组名称 = new 数据类型[数组长度]
int[] arrayA = new int[300];
double arrayB = new double[10];
String[] arrayC = new String[5];
静态的初始化(指定内容)
// 格式: 数据类型[] 数组名称 = new 数据类型[] {元素1, 元素2, 元素3}
int[] arrayA = new int[] { 5, 26, 98, 17 };
String[] arrayB = new String[] { "Java", "Python", "Node", "JavaScript" };
// 省略格式 数据类型[] 数组名称 = {元素1, 元素2, 元素3}
double[] arrayC = { 18.02, 10, 12.0, 59.99, 5 }
int[] arr = new int[] { 10, 589, 31, 20, 89 }
for(int i = 0; i < arr.length; i++) {
system.out.print(arr[i] + " ")
}
数组元素反转(不使用临时数组 使用临时变量)
// 法一:
for (int min = 0, max = array.length - 1; min < max ; min++, max--) {
int temp = array[min];
array[min] = array[max];
array[max] = temp;
}
// 法二:
for (int i = 0; i < array.length / 2; i++) {
int temp = array[i];
array[i] = array[array.length - (i + 1)];
array[array.length - (i + 1)] = temp;
}
数组工具类
int[] arr = { 14, 26, 8, 9, 4, 414, 258 };
String str = Arrays.toString(arr); // [14, 26, 8, 9, 4, 414, 258]
Arrays.sort(arr); // 按照升序进行排序
Random r = new Random();
// 随机生成 [0, 10) 之间的数
int res = r.nextInt(10)
猜数字小游戏
Random r = new Random();
// 要猜的随机数
int num = r.nextInt(100);
System.out.println("随机数是" + num);
Scanner sc = new Scanner(System.in);
System.out.println("请输入你猜的数:");
int guess = sc.nextInt();
int count = 5; // 有五次猜数的机会
while(true) {
if(guess > num) {
System.out.println(guess + "太大了!");
} else if(guess < num) {
System.out.println(guess + "太小了");
} else {
System.out.println("恭喜你猜对了");
break;
}
count--;
if(count == 0) {
System.out.println("你输了...");
break;
}
System.out.println("你还有" + count + "次机会!");
guess = sc.nextInt();
}
对象数组
Person[] person = new Person[2];
Person p1 = new Person("周杰伦", 40);
Person p2 = new Person("古月娜", 22);
person[0] = p1;
person[1] = p2;
Math.abs(3.14); // 3.14
Math.ceil(3.9); // 4.0
Math.floor(3.99); // 3.0
Math.round(3.49); // 3
Math.PI; // 3.141592653589793
Animal animal = new Cat();
animal.eat();
Dog dog = (Dog)animal // 会报转换类异常
Cat cat = (Cat)animal;
cat.catchMouse();
Animal animal = new Dog();
if(animal instanceof Dog) {
Dog dog = (Dog)animal;
dog.eat(); // 狗吃骨头
}
if(animal instanceof Cat) {
Cat cat = (Cat)animal;
cat.eat();
}
public class Weapon {
private String code; // 武器的代号
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Weapon() {
}
public Weapon(String code) {
this.code = code;
}
}
public class Hero {
private String name; // 英雄的名字
private int age; // 英雄的年龄
private Weapon weapon; // 武器
public Hero() {
}
public Hero(String name, int age, Weapon weapon) {
this.name = name;
this.age = age;
this.weapon = weapon;
}
public void attack() {
System.out.println("年龄为" + age + "的" + name + "用" + weapon.getCode() + "攻击敌方");
}
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 Weapon getWeapon() {
return weapon;
}
public void setWeapon(Weapon weapon) {
this.weapon = weapon;
}
}
// 创建英雄角色
Hero hero = new Hero();
// 设置名字
hero.setName("盖伦");
// 设置年龄
hero.setAge(20);
Weapon weapon = new Weapon("多兰剑");
// 配备武器
hero.setWeapon(weapon);
hero.attack(); // 年龄为20的盖伦用多兰剑攻击敌方
对于 ArrayList 集合来说,add 添加动作一定是成功的,所以返回值可不用,但对于其他集合来说,add 添加动作不一定成功
基本使用
// 泛型, 集合中存储的数据类型
ArrayList<String> list = new ArrayList<>();
// 往集合中添加元素
list.add("Java");
ArrayList 集合的常用方法和遍历
// 返回值是一个布尔值
boolean success = list.add("Node");
// 从集合中获取元素:get 索引从 0 开始
String lang = list.get(0);
// 从集合中删除元素:remove 索引从 0 开始
list.remove(0);
// 获取集合的长度
list.size();
// 遍历集合
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + " ");
}
如果希望向集合中存储基本数据类型,必须使用基本类型的 包装类
泛型只能是引用类型,不能是基本数据类型
ArrayList<Integer> list = new ArrayList<>();
list.add(15);
从 JDK 1.5+ 开始,支持自动装箱、自动拆箱
用集合来存储自定义学生对象
ArrayList<Student> list = new ArrayList<>();
Student one = new Student("Java", 56, "123");
Student two = new Student("JavaScript", 43, "159159");
list.add(one);
list.add(two)
System.out.print(list.get(1)); // JavaScript
创建字符串
String str1 = new String(); // 空的
// 根据字符数组创建字符串
char[] charArray = { 'A', 'b', 'c' };
String str2 = new String(charArray); // Abc
// 根据字节数组创建字符串
byte[] byteArray = { 65, 66, 67 };
String str3 = new String(byteArray); // ABC
// 直接创建
String str4 = "Java"; // Java
字符串的常量池5
String str1 = "java";
String str2 = "java";
char[] charArray = { 'j', 'a', 'v', 'a' };
String str3 = new String(charArray);
System.out.println(str1 == str2); // true
System.out.println(str2 == str3); // false
字符串的比较相关方法
// str1.equals(str2) // 比较的是内容
String str1 = "Java";
String str2 = "Java";
char[]charArray = { 'J', 'a', 'v', 'a' };
String str3 = new String(charArray);
System.out.println(str1.equals(str2)); // true
System.out.println(str1.equals(str3)); // true
System.out.println("Java".equals(str1)); // true
// 如果比较双方一个常量一个变量,推荐把常量字符串写在前面
String str5 = "";
"Java".equals(str5); // 不会报空指针异常
str5.equals("Java"); // 会报空指针异常
// equalsIgnoreCase() 比较时忽略大小写
String strA = "Java";
String stra = "java";
strA.equalsIgnoreCase(stra) // true
字符串的获取相关方法
String str1 = "Java";
String str2 = "JavaScript";
String str3 = str1.concat(str2); // str1 str2 的值不会改变
System.out.println(str3); // JavaJavaScript
char ch = str1.charAt(0); // J
str1.indexOf("va") // 2
字符串的截取方法
String str1 = "JavaScript";
String str2 = str1.substring(5); // cript
String str3 = str1.substring(2, 6); // vaSc
字符串的转换相关方法
String str1 = "JavaScript";
char[] charArray = str1.toCharArray();
System.out.println(charArray.length); // 10
// 转换成为字节数组
byte[] bytes = str1.getBytes();
System.out.println(bytes[1]); // 97
// Java(str1不会变,生成的是新的字符串)
System.out.println(str1.replace("JavaScript", "Java"));
字符串的分割方法
String str1 = "Java,Scr,ipt";
String[] splits = str1.split(",");
for (int i = 0; i < splits.length; i++) {
System.out.print(splits[i] + " "); // Java Scr ipt
}
// str1.split(".") 是切不成功的,str1.split("\\\.")是可以切成功的
Objects 类的 equals 方法
String a = "abc";
String b = "";
Objects.equals(b, a) // 是容忍空指针异常的
如果说父类中的方法不确定如何进行 {} 方法,那么这应该是一个 抽象方法
抽象方法和抽象类的格式
// 抽象类
public abstract class Animal {
// 抽象方法,代表吃东西,但是具体吃什么(大括号的内容)不确定
public abstract void eat();
}
// 普通的成员方法(抽象类中也可有成员方法)
public void getName(){}
抽象方法和抽象类的注意事项和使用
不能直接创建 new 抽象类对象(没有抽象方法的抽象类,也不能直接创建对象,在一些特殊场景下有用途)
可以有构造方法,是提供子类创建对象时,初始化父类成员使用的
抽象类中,不一定包含抽象方法,但又抽象方法的类必须是抽象类
子类必须覆盖重写抽象父类中所有的抽象方法
必须用一个子类来继承抽象方法
public class Cat extends Animal {
public Cat(String name) {
super(name);
}
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
// 使用
Animal cat1 = new Cat("Tom"); // 多态的写法 左边父类(抽象类)右边实现类
Cat cat2 = new Cat("JiMu");
cat1.eat(); // "猫吃鱼"
cat2.eat(); // "猫吃鱼"
发红包案例
用户类
// 用户类
public class User {
private String name;
private int money; // 余额
public User() {
}
public User(String name, int money) {
this.name = name;
this.money = money;
}
public void show() {
System.out.println("我是" + name + ", 我有多少钱:" + money);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
}
发红包的群主类
// 群主
public class Manager extends User {
public Manager() {
}
public Manager(String name, int money) {
super(name, money);
}
public ArrayList<Integer> send(int totalMoney, int count) {
// 用集合来存储若干个红包的余额
ArrayList<Integer> redList = new ArrayList<>();
// 看群主有多少余额
int leftMoney = super.getMoney();
if(totalMoney > leftMoney) { // 余额不足
System.out.println("余额不足!");
return redList;
}
// 扣钱
super.setMoney(leftMoney - totalMoney);
// 发红包,平均每个红包多少
int avg = totalMoney/count;
int mod = totalMoney % count; // 余数
// 剩下的零头,包在最后一个红包当中
// 下面准备包红包
for (int i = 0; i < count - 1; i++) {
redList.add(avg);
}
// 最后一个红包
int last = avg + mod;
redList.add(last);
return redList;
}
}
抢红包的成员类
// 普通成员
public class Member extends User {
public Member() {
}
public Member(String name, int money) {
super(name, money);
}
public void receive(ArrayList<Integer> list) {
// 从多个红包中随便抽取一个给自己
// 随机获取一个集合当中的编号
int index = new Random().nextInt(list.size());
// 根据索引从集合当中删除,并且得到被删除的红包给自己
int delta = list.remove(index);
// 当前成员有多少钱
int money = super.getMoney();
super.setMoney(money + delta);
}
}
测试类
public static void main(String[] args) {
Manager manager = new Manager("群主", 100); // 群主有100元
Member one = new Member("A", 0);
Member two = new Member("B", 0);
Member three = new Member("C", 0);
manager.show(); // 100
one.show(); // 0
two.show(); // 0
three.show(); // 0
System.out.println("==========================");
ArrayList<Integer> redList = manager.send(59, 3);
one.receive(redList);
two.receive(redList);
three.receive(redList);
manager.show();
one.show();
two.show();
three.show();
}
接口中可以包含的内容有
接口的 抽象方法定义
public interface MyInterface {
public abstract void methodA();
public void methodB();
abstract void methodC();
void methodD();
}
接口的使用步骤
// 注意事项:如果实现类并没有覆盖重写接口中所有的抽象方法,那么这个实现类必须是抽象类
// 实现类
public class MyInterfaceImpl implements MyInterface {
@Override
public void methodA() {
System.out.println("methodA");
}
}
接口中的默认方法定义
从 Java 8 开始,接口里允许定义默认方法
备注:接口当中的默认方法,可以解决接口升级的问题
public default 返回值类型 方法名称(参数列表) { 方法体 }
接口的默认方法使用
public default void methodDefault(){
System.out.println("这是一个新添加的默认方法");
}
// 创建实现类对象
MyInterfaceImpl myInterface = new MyInterfaceImpl();
myInterface.methodA();
// 调用默认方法,如果实现类当中没有,会向上找接口
myInterface.methodDefault();
接口的静态方法定义
public static 返回值类型 方法名称(参数列表) { 方法体 }
接口的静态方法使用
public static void methodStatic() {
System.out.println("这是接口的静态方法");
}
myInterface.methodStatic // 通过实现类的对象来.接口中的静态方法 => 错误写法
// 调用格式:接口名称.静态方法名(参数)
MyInterface.methodStatic(); // 这是接口的静态方法
接口的私有方法定义
普通私有方法格式:private 返回值类型 方法名称(参数列表) { 方法体 }
静态私有方法格式:private static 返回值类型 方法名称(参数列表) { 方法体 }
接口的私有方法使用
private void defaultCommon() {
System.out.println("AAA");
}
private static void StaticCommon() {
System.out.println("BBB");
}
接口的常量定义和使用
接口定义常量的格式 public static final 数据类型 常量名称 = 数据值;
// 使用格式:接口名称.常量名称
int num = MyInterface.NUM_OF_MY_CLASS;
接口的内容小结
static {} // 在接口中使用静态代码块是 => 错误写法
public MyInterface() {} // 在接口中使用构造方法是 => 错误写法
实现类可以实现多个接口,继承类只能继承一个类
一个实现类实现了多个接口且接口当中有重复的默认方法,实现类必须要覆盖重写该方法
一个接口可以继承多个接口,若继承的多个父接口当中默认方法重复,那么子接口必须进行默认方法的覆盖重写,并且带着 default 关键字
笔记本 USB 接口案例分析 使用接口和多态
案例实现
public interface USB {
public abstract void open(); // 打开设备
public abstract void close(); // 关闭设备
}
public class Laptop {
public void powerOn() {
System.out.println("笔记本电脑开机");
}
public void powerOff() {
System.out.println("笔记本电脑关机");
}
// 使用 USB 设备的方法,使用接口作为方法的参数
public void useDevice(USB usb) {
usb.open();
if(usb instanceof Mouse) {
Mouse mouse = (Mouse)usb;
mouse.click();
} else if(usb instanceof KeyBoard) {
KeyBoard keyBoard = (KeyBoard) usb;
keyBoard.type();
}
usb.close();
}
}
public class Mouse implements USB {
@Override
public void open() {
System.out.println("打开鼠标");
}
@Override
public void close() {
System.out.println("关闭鼠标");
}
public void click() {
System.out.println("鼠标点击");
}
}
public class KeyBoard implements USB {
@Override
public void open() {
System.out.println("打开键盘");
}
@Override
public void close() {
System.out.println("关闭键盘");
}
public void type() {
System.out.println("键盘按下");
}
}
// 创建笔记本对象
Laptop laptop = new Laptop();
laptop.powerOn();
// 向上转型
USB usb = new Mouse();
laptop.useDevice(usb);
laptop.useDevice(new KeyBoard());
laptop.powerOff();
英雄类 Hero
public class Hero {
private String name; // 英雄的名称
private Skill skill; // 英雄的技能
public Hero() {
}
public Hero(String name, Skill skill) {
this.name = name;
this.skill = skill;
}
public void attack() {
System.out.println("我叫" + name + ", 开始释放技能:");
skill.use();
System.out.println("释放技能完成");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Skill getSkill() {
return skill;
}
public void setSkill(Skill skill) {
this.skill = skill;
}
}
技能接口 Skill
public interface Skill {
void use(); // 释放技能的抽象方法
}
测试类
// 创建英雄角色
Hero hero = new Hero();
// 设置英雄名称
hero.setName("艾希");
// hero.setSkill(new SkillImpl()); 传实现类的方法
hero.setSkill(() -> System.out.println("哔哔哔")); // 传匿名对象的方法
hero.attack(); // 我叫艾希, 开始释放技能:哔哔哔 释放技能完成
成员内部类的定义
public class Body { // 外部类
public class Heart { // 成员内部类
// 内部类的方法
public void beat() {
System.out.println("蹦蹦蹦!");
System.out.println("内部类方法" + name);
}
}
// 外部类的方法
public void methodBody() {
System.out.println("外部类的方法");
new Heart().beat();
}
// 外部类的成员变量
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
成员内部类的使用
// 间接方式:在外部类的方法当中,使用内部类;然后在 main 只是调用外部类的方法
Body body = new Body();
// 通过外部类的对象,调用外部类的方法,里面间接在使用内部类 Heart
body.methodBody();
// 直接方式 【外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();】
Body.Heart heart = new Body().new Heart();
heart.beat();
内部类的同名变量访问
public class Outer {
int num = 10;
public class Inner {
int num = 20;
public void methodInner() {
int num = 30;
System.out.println(num); // 30
System.out.println(this.num); // 20
System.out.println(Outer.this.num); // 10
}
}
}
局部内部类
public class Outer {
public void methodOuter() {
class Inner {
int num = 10;
public void methodInner() {
System.out.println(num); // 10
}
}
Inner inner = new Inner();
inner.methodInner();
}
}
匿名内部类
MInterface mInterface = new MInterface() {
@Override
public void method() { // 匿名内部类实现的方法
System.out.println("hello");
}
};
mInterface.method(); // hello
//
new MInterface() {
@Override
public void method() { // 匿名内部类实现的方法
System.out.println("hello");
}
}.method();
红包分发策略
需要做的:
红包打开模式接口 OpenMode
public interface OpenMode {
/**
* 将totalMoney分成count份,保存到ArrayList中,返回即可。
*
* @param totalMoney 总金额为方便计算,已经转换为整数,单位为分。
* @param totalCount 红包个数
* @return ArrayList 元素为各个红包的金额值,所有元素的值累和等于总金额。
*/
ArrayList<Integer> divide(int totalMoney, int totalCount);
}
继承 RedPacketFrame 类,实现设置红包标题类 MyRed(生成红包界面类)
public class MyRed extends RedPacketFrame {
/**
* 构造方法:生成红包界面。
*
* @param title 界面的标题
*/
public MyRed(String title) {
super(title);
}
}
实现接口 OpenMode 的普通红包类 NormalMode
public class NormalMode implements OpenMode {
@Override
public ArrayList<Integer> divide(final int totalMoney, final int totalCount) {
ArrayList<Integer> list = new ArrayList<>();
int avg = totalMoney / totalCount; // 平均值
int mod = totalMoney % totalCount;
// totalCount -1 代表 最后一个先留着
for(int i = 0; i < totalCount -1; i++) {
list.add(avg);
}
// 有零头需要放在最后一个
list.add(avg + mod);
return list;
}
}
实现接口 OpenMode 的随机红包类 RandomMode
public class RandomMode implements OpenMode {
@Override
public ArrayList<Integer> divide(int totalMoney, int totalCount) {
ArrayList<Integer> list = new ArrayList<>();
// 随机分配, 有可能多, 有可能少
// 最少一分钱, 最多不超过剩下金额平均数的 2 倍 如发 10 块 发 3 个 下面分析
/*
第一次发红包, 随机范围是 0.01~6.66元
第一次发完之后, 剩下的至少是 3.34 元
此时还需再发 2 个红包 然后再发的范围是 0.01~3.34 元(取不到右边, 至少剩下0.01)
【范围的公式】:1 +random.nextInt(LeftMoney / LeftCount * 2);
*/
Random r = new Random(); // 首先随机创建一个随机生成器
// 定义两个变量, 分别代表剩下多少钱, 剩下多少份
int leftMoney = totalMoney;
int leftCount = totalCount;
// 随机发前 n-1 个, 最后一个不需要随机
for(int i = 0; i < totalCount - 1; i++) {
// 按照公式生成随机金额
int money = r.nextInt(leftMoney / leftCount * 2) + 1;
// 将一个随机红包放入集合
list.add(money);
// 剩下的金额
leftMoney -= money;
// 剩下应该再发的红包个数递减
leftCount--;
}
// 最后一个红包不需要随机, 直接放
list.add(leftMoney);
return list;
}
}
测试
public class Bootstrap {
public static void main(String[] args) {
MyRed red = new MyRed("传智博客双元课程");
red.setOwnerName("王思聪");
/*
普通红包
OpenMode normal = new NormalMode();
red.setOpenWay(normal);
*/
// 随机红包
OpenMode random = new RandomMode();
red.setOpenWay(random);
}
}
*
二. Java 进阶篇毫秒值(1000毫秒 == 1秒)
// 获取当前系统时间从 1970 年 1 余额 1 日 00:00:00 经历了多少毫秒
System.out.println(System.currentTimeMillis()); // 1610526602507
Date 类的构造方法和成员方法
// 构造方法
Date d = new Date(); // 无参的构造方法
System.out.println(d); // Wed Jan 13 16:33:16 CST 2021
Date d1 = new Date(0); // 有参的构造方法
System.out.println(d1); // Thu Jan 01 08:00:00 CST 1970
Date d2 = new Date(1610526602501L);
System.out.println(d2); // Wed Jan 13 16:30:02 CST 2021
// 成员方法
// 把日期转换为毫秒 相当于 System.currentTimeMillis()
System.out.println(d.getTime()); // 1610527026494
格式化日期 DateFormat(抽象类) 和 SimpleDateFormat 类
Date d = new Date();
DateFormat dFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(dFormat.format(d)); // 2021-01-13 16:50:40
// 将符合格式的日期时间字符串解析成 Date 日期
dateFormat.parse("2021-01-13 16:50:40"); // Wed Jan 13 16:50:40 CST 2021
练习 :计算从出生到现在有多少天
// 当前日期
Date now = new Date();
// 输入的格式
System.out.println("请输入您的生日 格式:YYYY-MM-dd");
DateFormat dFormat = new SimpleDateFormat("yyyy-MM-dd");
String birthdayStr = new Scanner(System.in).nextLine();
// 将输入的日期字符串解析为日期
Date birthday = dFormat.parse(birthdayStr);
// 计算天数
long days = (now.getTime() - birthday.getTime()) / 1000 / 3600 / 24;
System.out.println(days);
日历类 Calender (抽象类)
// 拿到 Calender 类的子类对象
Calendar calendar = Calendar.getInstance();
常用方法
// 获取日历
public static void getTimeStr(Calendar calendar) {
// 获取年
int year = calendar.get(Calendar.YEAR); // 2021
// 获取月, 范围 0-11
int month = calendar.get(Calendar.MONTH); // 1
// 获取多少号
int date = calendar.get(Calendar.DATE); // 13
int dateOfMonth = calendar.get(Calendar.DAY_OF_MONTH); // 13
System.out.println(year + "-" + month + "-" + date + " " + dateOfMonth);
}
// 拿到 Calender 类的子类对象
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR, 9999); // 9999-1-13 13
// 设置月的范围是 0-11
calendar.set(Calendar.MONTH, 9); // 9999-9-13 13
calendar.set(Calendar.DATE, 9); // 9999-9-9 9
// 同时设置年月日
calendar.set(8888, 8, 8); // 8888-8-8 8
// 把指定的字段增加或减少
calendar.add(Calendar.YEAR, 2); // 8890-8-8 8
calendar.add(Calendar.YEAR, -3); // 8887-8-8 8
System.out.println(calendar.getTime()); // Mon Sep 08 17:40:25 CST 8887
getTimeStr(calendar);
java.lang.System:提供了大量的静态方法,可以获取与系统相关的信息或系统级操作
// 获取系统时间
long currTime = System.currentTimeMillis(); // 1610531920738
// 拷贝数组
int[] src = {1, 3, 44, 75, 12, 36};
int[] dest = {58, 86 ,89, 40, 14, 5, 81};
System.out.println("复制前:" + Arrays.toString(dest)); // [58, 86, 89, 40, 14, 5, 81]
System.arraycopy(src, 0, dest, 0, 5);
System.out.println("复制后:" + Arrays.toString(dest)); // [1, 3, 44, 75, 12, 5, 81]
String 类:字符串是常量,它们的值在创建之后就不能改变
StringBuilder 类:字符串缓冲区,可以提高字符串的操作效率(看成一个长度可以变化的字符串)
StringBuilder 的构造方法和 append 方法(可以使用链式操作)
StringBuilder sb1 = new StringBuilder();
System.out.println(sb1); // 空
// append 方法返回的是 this, 调用方法的对象 sb2 所有使用 append 方法是无需接收返回值
StringBuilder sb2 = sb1.append("java"); // 使用 append 方法往 sb 中添加数据
sb1.append(" to me!");
System.out.println(sb1); // java to me!
System.out.println(sb1 == sb2); // true 两个对象是同一个对象
StringBuilder 的 toString()
String str1 = "java";
System.out.println(str1); // java
// 将 String 转换为 StringBuilder
StringBuilder sb = new StringBuilder(str1);
sb.append(" to me !");
System.out.println(sb); // java to me !
// 将 StringBuilder 对象转换为 String
String str2 = sb.toString();
System.out.println(str2); // java to me !
Java 提供了两个类型,基本类型和引用类型,使用基本类型在于效率,但是不能像对象那样做更多的功能,我们可以将基本类型转换为对应的包装类就可以使用更多的功能
装箱与拆箱
Integer int1 = new Integer(1); // // 构造方法已过时
Integer in2 = new Integer("2"); // 构造方法已过时
// 装箱
Integer in3 = Integer.valueOf(10);
Integer in4 = Integer.valueOf("1");
System.out.println(in3 + " " + in4);
// 拆箱
int i = in3.intValue();
System.out.println(i);
自动装箱与自动拆箱
Integer in = 1;
// 会自动拆箱再装箱
in = in + 2; // 3
ArrayList<Integer> list = new ArrayList<>();
list.add(1); // 自动装箱 list.add(Integer.valueOf(1))
int i = list.get(0); // 自动拆箱 list.get(0).intValue();
基本类型与字符串类型之间的相互转换
String str1 = 100 + "2";
String str2 = Integer.toString(200);
String str3 = String.valueOf(10);
System.out.println(str1 + str2 + str3); // 10020010
int i = Integer.parseInt(str1);
System.out.println(i); // 1002
模拟注册操作,如果用户已存在,则抛出异常并提示,亲,该用户名已存在
// RuntimeException 运行期异常, 出错后会交给 JVM 处理
// Exception 自己处理
public class RegisterException extends /*Exception*/ RuntimeException {
public RegisterException() {
}
public RegisterException(String message) {
super(message);
}
}
static String[] usernames = {"Java", "JavaScript", "Vue"};
public static void main(String[] args) {
System.out.println("请输入用户名");
String username = new Scanner(System.in).next();
checkUsername(username);
}
// 对用户输入进行校验
public static void checkUsername(String username) {
for (String name : usernames) {
if(name.equals(username)) {
/*
自己处理
try {
throw new RegisterException("亲, 该用户名已存在");
} catch (RegisterException e) {
e.printStackTrace();
return; // 结束方法
}
*/
throw new RegisterException("亲, 该用户名已存在"); // JVM 处理会中断程序
}
}
System.out.println("恭喜注册成功");
}
学习集合的目标:
Collection 接口:
Collection 集合常用方法(实现它的接口都可以使用)
Collection<String> coll = new ArrayList<>();
coll.add("java");
coll.add(" to me");
// 判断 java 元素是否在集合 coll 中
System.out.println(coll.contains("java")); // true
// 获取集合的长度
System.out.println(coll.size()); // 2
// 将集合转成一个数组
Object[] arr = coll.toArray();
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println(); // java to me
System.out.println(coll); // [java, to me]
// 将 java 元素删除
coll.remove("java");
System.out.println(coll); // [ to me]
// 清空集合中所有元素
coll.clear();
System.out.println(coll); // []
// 判断集合是否为空
System.out.println(coll.isEmpty()); // true
List 接口:
Set 接口:
不允许存储重复元素;
没有索引,无序(不能使用普通 for 循环遍历)
实现该接口的集合有:
TreeSet 集合(了解):底层是二叉树实现,一般用于排序
HashSet 集合(重点):底层是哈希表+(红黑树)实现的、不可以存储重复元素,存取无序
LinkedHashSet 集合(次之):底层是哈希表+链表实现,无索引,不可存储重复元素,可以保证存取顺序
斗地主案例
// 用牌存储组装好的牌
ArrayList<String> poker = new ArrayList<>();
// 存储牌的花色和牌的序号
String[] colors = { "♠", "♥", "♣", "♦" };
String[] numbers = { "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A" };
poker.add("大王");
poker.add("小王");
for(String number : numbers) {
for(String color : colors) {
// 牌一张一张的装进 poker 中
poker.add(color + number);
}
}
// 洗牌
Collections.shuffle(poker);
// 存储3个玩家的牌和底牌
ArrayList<String> play1 = new ArrayList<>();
ArrayList<String> play2 = new ArrayList<>();
ArrayList<String> play3 = new ArrayList<>();
ArrayList<String> diPai = new ArrayList<>();
for(int i = 0; i < poker.size(); i++) {
String p = poker.get(i);
if(i < 51) {
// 开始轮流发牌
if(i % 3 == 0) {
play1.add(p);
} else if(i % 3 == 1) {
play2.add(p);
} else if(i % 3 == 2) {
play3.add(p);
}
} else {
// 底牌
diPai.add(p);
}
}
// [♥8, ♣9, ♥2, ♣3, ♦Q, ♠7, ♠3, ♣8, ♦9, ♠10, ♥A, ♥4, ♥K, ♥7, ♦6, ♦J, ♦10]
System.out.println(play1);
// [♥5, ♣10, ♣K, 大王, ♠4, ♦7, 小王, ♣2, ♣J, ♦5, ♣5, ♥3, ♥Q, ♠Q, ♦4, ♣6, ♠6]
System.out.println(play2);
// [♠9, ♣Q, ♦K, ♥6, ♣4, ♠2, ♠8, ♦8, ♠A, ♠5, ♦2, ♣7, ♦3, ♣A, ♥10, ♠J, ♦A]
System.out.println(play3);
// [♥J, ♠K, ♥9]
System.out.println(diPai);
有序的(有索引),可以存储重复的元素
List 接口中的特有方法
List<String> list = new ArrayList<>();
list.add("Java");
list.add("JavaScript");
list.add(1, "to");
System.out.println(list); // [Java, to, JavaScript]
System.out.println(list.remove(1)); // to
System.out.println(list.set(1, "To")); // JavaScript 返回更新前的元素
System.out.println(list.get(1)); // To
System.out.println(list); // [Java, To]
ArrayList 实现类的底层是用数组来实现的,所以查询快,增删慢
LinkedList 实现类(用链表实现的,查询慢,增删快)
常用的方法(里面有大量操作首尾的方法)注:不能使用多态
LinkedList<String> linked = new LinkedList<>();
linked.addFirst("Java"); // 将 Java 添加到集合的第一个元素中
linked.addLast("JavaScript"); // 将 JavaScript 添加到集合的最后一个元素中
linked.add("bright"); // 等效于 addLast 方法
linked.addFirst("Like");
System.out.println(linked); // [Like, Java, JavaScript, bright]
linked.push("I"); // 等同于 addFirst 方法
System.out.println(linked); // [I, Like, Java, JavaScript, bright]
if(!linked.isEmpty()) { // 判断集合是否为空
System.out.println(linked.getFirst()); // I
System.out.println(linked.getLast()); // bright
}
// linked.clear(); 清空集合 获取时没有元素将报错
System.out.println(linked.removeFirst()); // I 移除并返回第一个元素
System.out.println(linked.removeLast()); // bright 移除并返回最后一个元素
System.out.println(linked.pop()); // 等同于 removeFirst 方法 Like 移除并返回第一个元素
System.out.println(linked); // [Java, JavaScript]
Vector 集合:底层也是由数组实现的(被 ArrayList 取代了)单线程的,所以较慢
Set 接口的特点
HashSet 集合实现类:底层是哈希表结构(查询的速度非常快)
Set<Integer> set = new HashSet<>();
set.add(10);
set.add(20);
set.add(30);
set.add(30); // 不允许重复的元素
Iterator<Integer> iter = set.iterator();
while(iter.hasNext()) {
System.out.print(iter.next() + " "); // 20 10 30
}
System.out.println();
for(Integer i : set) {
System.out.print(i + " "); // 20 10 30
}
HashSet 存储自定义类型元素
// 自定义存储 Person 类
HashSet<Person> set = new HashSet<>();
Person p1 = new Person("Java", 56);
Person p2 = new Person("Java", 56);
Person p3 = new Person("Java", 59);
set.add(p1);
set.add(p2);
set.add(p3);
// [Person{name='Java', age=56}, Person{name='Java', age=59}]
System.out.println(set);
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 使用 HashSet 存储该对象, 必须重写 equals 和 hashCode 方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
LinkedHashSet 集合实现类:底层是哈希表(数组+链表/红黑树)
// 元素不能重复, 有序
LinkedHashSet<String> set = new LinkedHashSet();
set.add("Java");
set.add("JavaScript");
set.add("Php");
set.add("Java"); // 不能重复
System.out.println(set); // [Java, JavaScript, Php]
Map 集合的特点
HashMap 实现类:底层是哈希表:查询的速度特别的快
LinkedHashMap 集合 extends HashMap
Map 接口中的常用方法
Map<String, String> map = new HashMap<>();
// 返回值 v1: key 存在, 替换并返回被替换的值, 所以返回 null
String v1 = map.put("language", "Java");
// key 存在, v 返回被删除的值; 不存在, v 返回 null
String language = map.remove("language"); // Java
map.put("language", "Java");
map.put("name", "Tom");
map.put("class", "软信1802");
System.out.println(map);
// map.get(key) 获取对应 key 的值
System.out.println(map.get("name")); // true
// 判断 map 集合中是否包含键 name
System.out.println(map.containsKey("name")); // true
Map 集合遍历的方式
Map<String, String> map = new HashMap<>();
map.put("age", "19");
map.put("language", "Java");
map.put("name", "Tom");
map.put("class", "软信1802");
// 获取 map 集合的所有 key 并放入 Set 集合中
Set<String> keys = map.keySet();
/*
使用的迭代器来遍历 Set 集合
Iterator iter = keys.iterator();
while(iter.hasNext()) {
String key = iter.next();
System.out.println(key + "=>" + map.get(key));
}
*/
// 使用增强 for 来遍历 Set 集合
for(String key : keys) {
System.out.println(key + "=>" + map.get(key));
}
Map<String, String> map = new HashMap<>();
map.put("age", "19");
map.put("language", "Java");
map.put("name", "Tom");
map.put("class", "软信1802");
// 使用 map.entrySet() 取出 map 集合所有的键值对对象
Set<Map.Entry<String, String>> set = map.entrySet();
// 通过迭代器遍历
Iterator<Map.Entry<String, String>> iter = set.iterator();
while(iter.hasNext()) {
Map.Entry<String, String> entry = iter.next();
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "=>" + value);
}
/*
通过 增加 for 循环遍历
for(Map.Entry entry : set) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "=>" + value);
}
*/
HashMap 存储自定义类型键值
/*
HashMap map = new HashMap<>();
map.put("北京", new Person("Java", 59));
map.put("上海", new Person("JavaScript", 40));
map.put("深圳", new Person("Node", 36));
map.put("杭州", new Person("Vue", 30));
Set set = map.keySet();
for(String key : set) {
System.out.println(key + "=>" + map.get(key));
}
*/
// Person 作为键
HashMap<Person, String> map = new HashMap<>();
map.put(new Person("Java", 59), "北京");
map.put(new Person("Java", 59), "上海"); // 值会被覆盖
map.put(new Person("Vue", 36), "深圳");
map.put(new Person("Node", 30), "杭州");
Set<Person> set = map.keySet();
for(Person key : set) {
System.out.println(key + "=>" + map.get(key));
}
/*
输出
Person{name='Node', age=30}=>杭州
Person{name='Vue', age=36}=>深圳
Person{name='Java', age=59}=>上海
*/
LinkedHashMap 集合 继承了 HashMap 集合,是一个有序的集合
LinkedHashMap<String, Integer> linkedMap = new LinkedHashMap<>();
linkedMap.put("Java", 59);
linkedMap.put("JavaScript", 50);
linkedMap.put("Vue", 36);
linkedMap.put("Node", 19);
System.out.println(linkedMap); // {Java=59, JavaScript=50, Vue=36, Node=19}
HashTable 集合(最早期的双列集合)implements Map
Hashtable<String, String> map = new Hashtable<>();
map.put(null, ""); // 抛空指针异常
练习:计算一个字符串中每个字符出现的次数
HashMap<Character, Integer> map = new HashMap<>();
String str = new Scanner(System.in).next(); // 输入 159159++
char[]charArray = str.toCharArray(); // 将字符串转为字符数组
for (int i = 0; i < charArray.length; i++) {
if(map.containsKey(charArray[i])) { // 判断 map 集合是否已经存在 key
map.put(charArray[i], map.get(charArray[i]) + 1);
} else { // 第一次出现的 字符
map.put(charArray[i], 1);
}
}
System.out.println(map); // {1=2, 5=2, 9=2, +=2}
List接口,Set 接口,Map 接口:里面增加了一个静态的方法,可以给集合一次性添加多个元素
List<? extends Serializable> list = List.of('a', "b", "c");
System.out.println(list); // [a, b, c]
看牌时是排好序的
// 准备牌
HashMap<Integer, String> poker = new HashMap<>();
// 存储牌的索引
ArrayList<Integer> pokerIndex = new ArrayList<>();
List<String> colors = List.of("♠", "♥", "♣", "♦");
List<String> numbers = List.of("2", "A", "k", "Q", "J", "10", "9", "8", "7", "6", "5", "4", "3");
// 先将大王小王存在 poker 中
poker.put(0, "大王");
pokerIndex.add(0);
poker.put(1, "小王");
pokerIndex.add(1);
int index = 2;
// 存储剩余 52 张牌
for (String number : numbers) {
for (String color : colors) {
poker.put(index, number);
pokerIndex.add(index);
index++;
}
}
// 洗牌
Collections.shuffle(pokerIndex);
// 存储三个玩家和底牌的索引
ArrayList<Integer> play1 = new ArrayList<>();
ArrayList<Integer> play2 = new ArrayList<>();
ArrayList<Integer> play3 = new ArrayList<>();
ArrayList<Integer> diPai = new ArrayList<>();
for (int i = 0; i < pokerIndex.size(); i++) {
Integer in = pokerIndex.get(i);
if(i >= 51) {
diPai.add(in);
} else {
if(i % 3 ==0) {
play1.add(in);
} else if(i % 3 == 1) {
play2.add(in);
} else if(i % 3 == 2) {
play3.add(in);
}
}
}
// 将牌拍好序
Collections.sort(play1);
Collections.sort(play2);
Collections.sort(play3);
Collections.sort(diPai);
// 看牌
// bright:2 2 A k Q 10 10 9 9 8 7 6 5 5 4 4 3
lookPoker("bright", poker, play1);
// tom:大王 小王 2 A A A k k Q J J 10 8 7 7 6 5
lookPoker("tom", poker, play2);
// lucy:2 k Q Q 10 9 9 8 7 6 6 5 4 4 3 3 3
lookPoker("lucy", poker, play3);
// J J 8
lookPoker("diPai", poker, diPai);
}
// 看牌
public static void lookPoker(String name, HashMap<Integer, String> poker, ArrayList<Integer> list) {
System.out.print(name + ":");
for (Integer key : list) {
System.out.print(poker.get(key) + " ");
}
System.out.println();
}
迭代:即 Collection 集合元素的通用获取方式。在取出元素之前要判断集合中有没有元素,如果有就把这个元素取出来,继续循环判断…
Iterator 接口(两个常用方法
获取迭代器:使用 Collection 接口中的方法 iterator(),该方法就是迭代器的实现类对象
Collection<String> coll = new ArrayList<>();
coll.add("Java");
coll.add("JavaScript");
coll.add("Php");
coll.add("Node.js");
coll.add("Vue.js");
Iterator<String> iter = coll.iterator();
while(iter.hasNext()) {
String item = iter.next();
System.out.print(item + " "); // Java JavaScript Php Node.js Vue.js
}
增强 for循环
// 格式:for(集合/数组 的数据类型 变量名 : 集合名/数组名)
Collection<String> coll = new ArrayList<>();
coll.add("Java");
coll.add("JavaScript");
for(String item : coll) {
System.out.print(item + " "); // Java JavaScript
}
使用前提(JDK1.5之后出现的新特性)
// 计算 0-n 的和
public static int add(int...arr) {
System.out.println(arr); // [I@723279cf 底层是一个数组
int sum = 0;
for (int i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
// 调用
System.out.println(add(12,20)); // 32
常用的功能
ArrayList<String> list = new ArrayList<>();
// 一次性往集合中添加多个元素
Collections.addAll(list, "Java", "JavaScript", "Php");
// 打乱集合中的顺序
Collections.shuffle(list);
// [Php, Java, JavaScript]
System.out.println(list);
// 按照默认升序进行排序
Collections.sort(list);
// [Java, JavaScript, Php]
System.out.println(list);
public static void sort(List list, Comparator)
ArrayList<Person> lPerson = new ArrayList<>();
lPerson.add(new Person("Java", 59));
lPerson.add(new Person("JavaScript", 56));
lPerson.add(new Person("Php", 39));
Collections.sort(lPerson);
// [Person{name='Php', age=39}, Person{name='JavaScript', age=56}, Person{name='Java', age=59}]
System.out.println(lPerson);
public class Person implements Comparable<Person> {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public int getAge() {
return age;
}
// 重写排序的规则
@Override
public int compareTo(Person o) {
// return 0; 认为元素都是相同的
// 按照人的年龄升序进行排序
return this.getAge() - o.getAge();
}
}
Comparator 和 Comparable 的区别
Comparator 的排序规则:o1 - o2 升序 o2 - o1 降序
ArrayList<Integer> list = new ArrayList<>();
list.add(10);
list.add(30);
list.add(19);
System.out.println(list); // [10, 30, 19]
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2; // 升序
// return o2 - o1; 降序
}
});
System.out.println(list); // [10, 19, 30]
Comparator 比较自定义类型
ArrayList<Student> list = new ArrayList<>();
list.add(new Student("Java", 59));
list.add(new Student("JavaScript", 59));
list.add(new Student("Php", 39));
Collections.sort(list, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
int res = o1.getAge() - o2.getAge();
// 如果两个人年龄相同, 再使用姓名的第一个字母
if(res == 0) {
res = o1.getName().charAt(0) - o2.getName().charAt(0);
}
return res;
}
});
// [Student{name='Php', age=39}, Student{name='Java', age=59}, Student{name='JavaScript', age=59}]
System.out.println(list);
并发与并行
并发:指两个或多个事件在同一个时间段内发生(交替执行)
并行:指两个或多个事件在同一时刻发生(同时执行,要快一些)
主线程:执行主(main)方法的线程
创建多线程程序的方式一(继承 Thread 类)
步骤
创建一个 Thread 类的子类
在 Thread 的子类中重写 Thead 类中的 run 方法,设置线程任务
创建 Thread 类的子类对象
调用 Thread 类中的方法 start() 方法,开启新的线程,执行 run 方法
// 创建 Thread 类的子类
public class ThreadDemo01 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("run => " + i); // 与 main 中的 for 是随机打印的
}
}
}
ThreadDemo01 td = new ThreadDemo01();
td.start(); // 启动 ThreadDemo01 线程
for (int i = 0; i < 10; i++) {
System.out.println("main => " + i);
}
Thread 类
public ThreadDemo01(String name) {
super(name);
}
ThreadDemo01 td = new ThreadDemo01("hello");
td.start();
System.out.println(td.getName()); // hello
System.out.println(td.currentThread().getName()); // main
td.setName("Java"); // 设置线程名称
System.out.println(td.getName()); // Java
td.sleep(1000); // 线程休眠一秒钟
创建多线程程序的方式二(实现 Runnable 实现类)
// 创建实现类
public class RunnableImpl implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " => " + i);
}
}
}
RunnableImpl run = new RunnableImpl();
Thread t = new Thread(run);
t.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " => " + i);
}
Thread 和 Runnable 的区别
匿名内部类方式实现线程的创建
new Thread() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " ---> Thread");
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " --> Runnable");
}
}
}).start();
下面是线程安全问题案例
//实现卖票任务
public class RunnableImpl implements Runnable {
// 定义一个多个线程共享的票源
private int ticket = 100;
@Override
public void run() {
// 使用死循环, 让卖票功能操作重复执行
while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 判断票源是否还有
if (ticket > 0) { // 票还有
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "票");
ticket--;
} else {
break;
}
}
}
}
// 测试
// 模拟买票案例
RunnableImpl run = new RunnableImpl();
// 创建三个线程
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
Thread t3 = new Thread(run);
// 开启三个线程
t1.start();
t2.start();
t3.start();
同步代码块
public class RunnableImpl implements Runnable {
// 定义一个多个线程共享的票源
private int ticket = 100;
// 创建一个锁对象
Object obj = new Object();
@Override
public void run() {
// 使用死循环, 让卖票功能操作重复执行
while (true) {
synchronized (obj) {
// 判断票源是否还有
if (ticket > 0) { // 票还有
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "票");
ticket--;
}
}
}
}
}
同步方法
public void run() {
// 使用死循环, 让卖票功能操作重复执行
while (true) {
payTicket();
}
}
public /* 使用了 synchronized 关键字 下面不需要加synchronized (this) */ void payTicket() {
synchronized (this) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 判断票源是否还有
if (ticket > 0) { // 票还有
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "票");
ticket--;
}
}
}
静态方法(也可以保证安全问题)
private static int ticket = 100;
public static /*synchronized*/ void payTicket() {
synchronized (Runnable.class) { // 这儿不是 this 而是 Runnable.class
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 判断票源是否还有
if (ticket > 0) { // 票还有
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "票");
ticket--;
}
}
}
解决线程安全问题的三种方案:使用 lock 锁
// 创建一个 ReentrantLock
Lock l = new ReentrantLock();
@Override
public void run() {
// 使用死循环, 让卖票功能操作重复执行
while (true) {
l.lock();
try {
Thread.sleep(100);
// 判断票源是否还有
if (ticket > 0) { // 票还有
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "票");
ticket--;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally { // 无论是否会出现异常, 都会释放锁
l.unlock();
}
}
}
调用 wait 和 notify 方法需要注意的细节
等待与唤醒案例(做包子与吃包子案例)
Object obj = new Object();
// 创建顾客线程
new Thread() {
@Override
public void run() {
// 保证等待和唤醒的线程只能有一个执行
while(true) {
synchronized (obj) {
System.out.println("告知老板的包子的数量");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 唤醒之后执行的代码
System.out.println("包子好了, 顾客准备开吃");
System.out.println("=================");
}
}
}
}.start();
// 创建老板线程
new Thread() {
@Override
public void run() {
while(true) {
try {
Thread.sleep(1000); // 2 秒钟做好包子
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj) {
System.out.println("老板1秒钟之后做好包子,可以吃包子了");
obj.notify();
}
}
}
}.start();
唤醒的方法
obj.notifyAll();
吃货与包子铺的故事案例
public class BaoZi {
public String pi;
public String xian;
// 包子的状态
boolean flag = false;
}
public class BaoZiPu extends Thread {
private BaoZi bz;
public BaoZiPu(BaoZi bz) {
this.bz = bz;
}
@Override
public void run() {
int count = 0;
while(true) {
synchronized (bz) {
if(bz.flag) { // 有包子
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 被唤醒之后执行, 包子铺生产包子
if(count % 2 ==0) {
// 生产薄皮三鲜馅包子
bz.pi = "薄皮";
bz.xian = "三鲜馅";
} else {
// 生产冰皮牛肉大葱包子
bz.pi = "冰皮";
bz.xian = "牛肉大葱";
}
count++;
System.out.println("包子铺正在生产:" + bz.pi + bz.xian + "包子");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 生产后修改包子的状态为 true
bz.flag = true;
// 唤醒吃货线程吃包子
bz.notify();
System.out.println("包子铺已经生产好了:" + bz.pi + bz.xian + "包子,吃货可以开吃了...");
}
}
}
}
public class CiHuo extends Thread {
private BaoZi bz;
public CiHuo(BaoZi bz) {
this.bz = bz;
}
@Override
public void run() {
while(true) {
synchronized (bz) {
if(!bz.flag) {
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 被唤醒之后就吃包子
System.out.println("吃货正在吃:" + bz.pi + bz.xian + "包子");
// 吃货吃完了
bz.flag = false;
// 唤醒包子铺生产
bz.notify();
System.out.println("吃货已经把" + bz.pi + bz.xian + "包子吃完了");
System.out.println("====================");
}
}
}
}
// 创建包子对象
BaoZi bz = new BaoZi();
// 创建包子铺线程, 开启生产包子
new BaoZiPu(bz).start();
// 创建吃货线程, 吃货吃包子
new CiHuo(bz).start();
线程池带来的好处
线程池的代码实现
public class RunnableImpl implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "创建了一个新的线程");
}
}
// 使用线程池
ExecutorService es = Executors.newFixedThreadPool(2);
// 线程池会一直开启, 使用完了线程会自动将线程归还给线程池继续使用
es.submit(new RunnableImpl());
es.submit(new RunnableImpl());
es.submit(new RunnableImpl());
es.submit(new RunnableImpl());
es.shutdown(); // 不建议使用(会将线程池销毁)
在 Runnable 中的使用
// Lambda 表达式标准格式
(参数类型 参数名称) -> { 代码语句 }
new Thread(() -> System.out.println("run2...执行了")).start();
无参数返回值的练习
public interface Cook {
void makeFood();
}
/*
// 不使用 Lamada 表达式
invokeCook(new Cook() {
@Override
public void makeFood() {
System.out.println("吃饭了");
}
});
*/
// Lambda 表达式
invokeCook(() -> System.out.println("吃饭了"));
public static void invokeCook(Cook cook) {
cook.makeFood();
}
Lambda 表达式有参数有返回值的练习
// 按照年龄进行排序
Arrays.sort(arr, (o1, o2) -> o1.getAge() - o2.getAge());
Lambda 表达式有参数有返回值的接口
public interface Calculator {
int calc(int a, int b);
}
public static void invokeCalc(int a, int b, Calculator c) {
int sum = a + b;
System.out.println(sum);
}
invokeCalc(19, 201, (a, b) -> a + b);
Lambda 省略格式 Lambda 使用前提
invokeCalc(19, 201, (a, b) -> a + b);
File 类的静态方法
String ps = File.pathSeparator;
System.out.println(ps); // 路径分隔符 windows:分号(;) linux:冒号{:}
String s = File.separator;
System.out.println(s); // \
File 类的构造方法
File f1 = new File("1.txt");
System.out.println(f1); // 1.txt
File f2 = new File("C://", "2.txt");
System.out.println(f2); // C:\2.txt
File parent = new File("d://");
File f3 = new File(parent, "3.txt");
System.out.println(f3); // d:\3.txt
File 类获取功能的方法
File f1 = new File("com\\bright");
System.out.println(f1.getAbsolutePath()); // E:\javaProjects\JavaSE\basic-code\com\bright
System.out.println(f1.getPath()); // com\bright
// 在 File 类中 toString() 方法就是调用的 getPath() 方法
System.out.println(f1.toString()); // com\bright
// 就是路径结尾的部分
System.out.println(f1.getName()); // bright
File f2 = new File("D:\\node.exe");
System.out.println(f2.length()); // 56467104
System.out.println(f1.length()); // 0 文件夹是没有大小之分的, 所以为0
File 类判断功能的方法
File f1 = new File("E:\\javaProjects\\JavaSE\\basic-code\\basic02-code\\basic02-code.iml");
System.out.println(f1.exists()); // true
System.out.println(f1.isDirectory()); // false
System.out.println(f1.isFile()); // true
创建删除功能的方法
// 如果 f1 不存在则返回 true,反之返回 false
File f1 = new File("E:\\1.txt");
boolean nF = f1.createNewFile();
System.out.println(nF); // true
// 如果 f2 存在则返回 false, 反之返回 true
File f2 = new File("E:\\hello");
System.out.println(f2.mkdir()); // true
// 创建多级文件夹
File f3 = new File("E:\\hello\\test");
System.out.println(f3.mkdirs()); // true
// 删除文件或者目录
File f4 = new File("E:\\hello");
System.out.println(f4.delete()); // true
File 类遍历(文件夹)目录的功能
File f1 = new File("E:\\整改");
String[] list = f1.list();
for (int i = 0; i < list.length; i++) {
System.out.println(list[i]);
}
File[] filesList = f1.listFiles();
for (int i = 0; i < files.length; i++) {
System.out.println(filesList[i]);
}
计算 n 的阶乘
System.out.println(jieC(8)); // 40320
public static int jieC(int n) {
if(n == 1) {
return 1;
}
return n*jieC(n-1);
}
递归打印多级目录
public static void getAllFile(File dir) {
File[] files = dir.listFiles();
for (File file : files) {
if(file.isDirectory()) {
getAllFile(file);
} else {
System.out.println(file);
}
}
}
// 调用(打印 f1 下面的所有目录和文件夹
File f1 = new File("E:\\软件测试");
getAllFile(f1);
综合案例(文件搜索)
File f1 = new File("E:\\软件测试");
getDocxFile(f1);
public static void getDocxFile(File dir) {
File[] files = dir.listFiles();
for (File file : files) {
if(file.isDirectory()) {
getDocxFile(file);
} else {
if(file.getName().endsWith(".docx")) {
System.out.println(file);
}
}
}
}
FileFilter 过滤器的原理和使用
File[] listFiles (FileFilter filter)
java.io.FileFilter接口:用于抽象路径名(File 对象)的过滤器
作用:过滤文件(File 对象)
抽象方法:用来过滤文件的方法
File[] listFiles(FilenameFilter filter)
注意:两个过滤器接口没有实现类
public class FileFilterImpl implements FileFilter {
@Override
public boolean accept(File pathname) {
return pathname.getName().endsWith(".docx") || pathname.isDirectory();
}
}
public static void getAllFile(File dir) {
File[] files = dir.listFiles(new FileFilterImpl());
for (File file : files) {
if(file.isDirectory()) {
getAllFile(file);
} else {
System.out.println(file);
}
}
}
File f1 = new File("E:\\软件测试");
getAllFile(f1);
使用 Lambda 表达式优化(匿名内部类)
public static void getAllFile(File dir) {
File[] files = dir.listFiles((ldi, name) -> new File(ldi, name).isDirectory() || name.endsWith("docx"));
for (File file : files) {
if(file.isDirectory()) {
getAllFile(file);
} else {
System.out.println(file);
}
}
}
File f1 = new File("E:\\软件测试");
getAllFile(f1);
java.io.OutputStream 抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地
字节输出流的子类基本共性方法
public void close():关闭输出流并释放系统资源(在完成流的操作后必须释放系统资源
public void flush():刷新输出流并强制缓冲的输出字节被写出
public void write(byte[] b):将 b.length 字节从指定的字节数组写入输出流
public void write(byte[] b, int off, int len):从指定的字节数组写入 len 字节,从偏移量 off 开始输出
public abstract void write(int b):将指定的字节输出流(写一个)
FileOutputStream (将内存中的数据写入到硬盘的文件中)
FileOutputStream fos = new FileOutputStream(".\\1.txt");
// fos.write(85); 写一个字节
byte[]bytes = {97,65,69,100}; // 一次性写多个字节
fos.write(bytes);
/*
byte[]bytes = {65, 66, 67, 68, 69};
fos.write(bytes, 1, 2); BC 从 1 索引开始写, 写 2 个
*/
/*
byte[] bytes = "Hello 你好啊!".getBytes(); 将字符串转为字节数组
fos.write(bytes); // Hello 你好啊!
*/
fos.close();
字节输出流的续写和换行
FileOutputStream fos = new FileOutputStream(".\\1.txt", true);// 第二个参数为 true 就续写
byte[] bytes = "续写".getBytes();
// byte[] bytes = "换行\r\n".getBytes(); \r\n 换行
fos.write(bytes); // Hello 你好啊!
fos.close();
InputStream 类是抽象类,是所有字节输入流的超类
所有子类共性的方法
FileInputStream 字节输入流:将硬盘文件中的数据读取到内存中使用
构造方法
String 类的构造方法
FileInputStream fis = new FileInputStream(".\\1.txt");
int len = -1;
/*
while((len = fis.read()) != -1) {
System.out.println((char)len);
}
*/
byte[]car = new byte[10];
while((len = fis.read(car)) != -1) {
String str = new String(car, 0, len);
System.out.println(str);
}
fis.close();
复制文件
FileInputStream fis = new FileInputStream(".\\1.txt");
FileOutputStream fos = new FileOutputStream("E:\\2.txt");
byte[] car = new byte[1024];
int len = -1; // 每次读取到的有效数据(字节)
while((len = fis.read(car)) != -1) {
fos.write(car, 0, len); // len 是每次读取到的数据(字节)
}
fos.close();
fis.close();
Reader 类:字符输入流,是抽象类,是字符输入流的超类
共性的成员方法
java.io.FileReader extends InputStreamReader extends Reader
FileReader:文件字符输入流
FileReader fr = new FileReader(".\\1.txt");
char[] car = new char[1024];
int len = -1;
while((len = fr.read(car)) != -1) {
String str = new String(car, 0, len);
System.out.print(str);
}
fr.close();
java.io.Writer:字符输出流,是所有字符输出流的超类,是抽象类
共性的方法
java.io.FileWriter extends OutputStreamWriter extends Writer
FileWriter(文件输出流):将内存中的字符数据写入到指定的文件中
构造方法
FileWriter fw = new FileWriter(".\\2.txt");
FileReader fr = new FileReader(".\\1.txt");
int len = -1;
char[] car = new char[1024];
while((len = fr.read(car)) != -1) {
String str = new String(car, 0, len);
System.out.println(str);
fw.write(car);
}
fw.close();
fr.close();
flush 方法和 close 方法的区别
java.util.Properties 集合 extends HashTable
Properties 类表示了一个持久的属性集。 Properties 可以保存流中加载
Properties 集合是唯一与 IO 流相结合的集合
Properties prop = new Properties();
prop.setProperty("Java", "59");
prop.setProperty("JavaScript", "50");
prop.setProperty("Node", "16");
// prop.put(1, true);
Set<String> set = prop.stringPropertyNames();
for (String key : set) {
// Java => 59 JavaScript => 50 Node => 16
System.out.print(key + " => " + prop.getProperty(key) + " ");
}
// 把集合中的临时数据,持久化到硬盘上存储
FileWriter fw;
try {
fw = new FileWriter(".\\1.txt");
prop.store(fw, "save data");
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
// 把硬盘中保存的文件(键值对)读取到 Properties 集合中使用
Properties prop = new Properties();
FileReader fr = new FileReader(".\\1.txt");
prop.load(fr);
// {Java=59, JavaScript=50, Node=16}
System.out.println(prop);
BufferedOutputStream:字节缓冲输出流
构造方法
BufferedInputStream:字节缓冲输入流
构造方法
long start = System.currentTimeMillis();
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(".\\1.mp4"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(".\\2.mp4"));
int len = -1;
byte[] car = new byte[1024*60];
while((len = bis.read(car)) != -1) {
bos.write(car, 0, len);
}
long end = System.currentTimeMillis();
System.out.println(end - start + "ms"); // 130ms
bos.close();
bis.close();
BufferedWriter:字符缓冲输出流(最后面需要 flush 一下)
构造方法
特有的成员方法
BufferedWriter bw = new BufferedWriter(new FileWriter(".\\1.html"));
bw.write("hello");
bw.newLine();
bw.write("java");
bw.flush();
BufferReader:字符缓冲输入流
构造方法
特有的成员方法
String str = br.readLine();
System.out.println(str); //
复制文件文件
long start = System.currentTimeMillis();
BufferedReader br = new BufferedReader(new FileReader(".\\node.html"));
BufferedWriter bw = new BufferedWriter(new FileWriter(".\\1.html"));
int len;
char[] car = new char[1024*10];
while((len = br.read(car)) != -1) {
String str = new String(car, 0, len);
System.out.println(str);
bw.write(car);
}
bw.close();
br.close();
long end = System.currentTimeMillis();
System.out.println(end - start + "ms"); // 49ms
对文本文件内容进行排序(对出师表内容)
// 用来存储每一行
HashMap<String, String> map = new HashMap<>();
BufferedReader br = new BufferedReader(new FileReader(".\\10.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter(".\\11.txt"));
// 每一行读取的数据
String line;
while((line = br.readLine()) != null) {
String[] arr = line.split("\\.");
map.put(arr[0], arr[1]);
}
for (String key : map.keySet()) {
String value = map.get(key);
line = key + "." + value;
bw.write(line);
bw.newLine();
}
// 释放资源
bw.close();
br.close();
编码:字符(能看懂的)——字节(看不懂的)
解码:字节(看不懂的)——字符(能看懂的)
java.io.OutputStreamWriter extends Writer
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(".\\1.txt"), "utf-8"); // 默认是 utf-8 编码
osw.write("你好");
osw.flush();
osw.close();
java.io.InputStreamReader extends Reader
InputStreamReader isr = new InputStreamReader(new FileInputStream(".\\1.txt"), "utf-8"); // 默认是 utf-8 解码
int len = -1;
while((len = isr.read()) != -1) {
System.out.print((char)len); // 你好
}
isr.close();
使用 将 gbk 的文件复制成 utf-8 格式的文件
InputStreamReader isr = new InputStreamReader(new FileInputStream(".\\1.txt"), "gbk");
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(".\\2.txt"));
int len = -1;
char[] car = new char[1024];
while((len = isr.read(car)) != -1) {
osw.write(car, 0, len);
}
osw.close();
isr.close();
序列化和反序列化的类必须实现 java.io.Serializable 接口(标记接口)
java.io.ObjectOutputStream extends OutputStream (序列化流) 序列化都是对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(".\\3.txt"));
oos.writeObject(new Person("JavaScript", 59));
oos.close();
java.io.ObjectInputStream(InputStream in) extends InputStream**(反序列化流)**
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(".\\3.txt"));
Person o = (Person)ois.readObject();
ois.close();
System.out.println(o);
如果不需要将某一个字段被序列化,在修饰符后面加上 transient 瞬态关键字
private transient String name;
Person{name='null', age=59} // name 字段不会被序列化
序列化之后,序列化的类发生了改变后,在反序列化会抛出异常(InvalidClassException)
// 在被序列化的类中加入
private static final long serialVersionUID = 42L;
序列化集合(练习)
// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(".\\3.txt"));
LinkedList<Person> list = new LinkedList<>();
list.add(new Person("Java", 59));
list.add(new Person("JavaScript", 46));
list.add(new Person("Node", 20));
oos.writeObject(list);
oos.close();
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(".\\3.txt"));
LinkedList<Person> list = (LinkedList<Person>)ois.readObject();
ois.close();
System.out.println(list);
java.io.PrintStream extends OutputStream:打印流
PrintStream ps = new PrintStream(".\\1.txt");
ps.write(97); // a
ps.write(13); // 回车
ps.println("hello"); // hello
ps.print(97); // 97
// 改变打印流的目的地, 一般是打印在控制台, 在这儿是打印到 1.txt 中
System.setOut(ps);
System.out.println("我在打印流的 setOut 中");
ps.close();
TCP 通信的客户端代码实现
Socket socket = new Socket("127.0.0.1", 8989);
OutputStream os = socket.getOutputStream();
os.write("你好服务器".getBytes());
InputStream is = socket.getInputStream();
byte[] car = new byte[1024];
int len = is.read(car);
System.out.println(new String(car, 0, len));
socket.close();
TCP 通信的服务端代码实现
TCP 通信客户端:接收客户端的请求,读取客户端发送的数据,给客户端回写数据
表示服务器端的类
构造方法
服务端必须知道是哪台客户端请求的服务器,可以使用 accept 方法获取到请求的客户端对象 Socket
成员方法
ServerSocket server = new ServerSocket(8989);
Socket socket = server.accept();
InputStream is = socket.getInputStream();
byte[] car = new byte[1024];
int len = is.read(car);
System.out.println(new String(car, 0, len));
OutputStream os = socket.getOutputStream();
os.write("收到ok".getBytes());
socket.close();
server.close();
文件上传案例的客户端
文件上传案例阻塞问题
// 读取被上传的文件
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(".\\1.mp4"));
Socket socket = new Socket("127.0.0.1", 8989);
// 发送给服务端的缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
int len = -1;
byte[] car = new byte[1024];
while((len = bis.read(car)) != -1) {
bos.write(car, 0, len);
}
// 给服务端写一个结束标记
socket.shutdownOutput();
// 读取服务端返回的数据
InputStream is = socket.getInputStream();
while((len = is.read(car)) != -1) {
System.out.println(new String(car, 0, len));
}
bis.close();
socket.close();
文件上传案例的客户端
// 开启服务器
ServerSocket server = new ServerSocket(8989);
// 获取客户端的 Socket 对象
Socket socket = server.accept();
// 读取客户端的缓冲输入流
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
// 将服务器端的数据写在硬盘上
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(".\\2.mp4"));
int len = -1;
byte[] car = new byte[1024];
while((len = bis.read(car)) != -1) {
bos.write(car, 0, len);
}
// 返回给客户端的输出流对象
OutputStream os = socket.getOutputStream();
os.write("上传完毕!".getBytes());
bos.close();
socket.close();
使用多线程提高效率
ServerSocket server = new ServerSocket(8989);
while(true) {
// 使用多线程技术提高效率(有一个客户端上传文件, 就开启一个线程
new Thread(() -> {
try {
Socket socket = server.accept();
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(".\\2.mp4"));
int len = -1;
byte[] car = new byte[1024];
while((len = bis.read(car)) != -1) {
bos.write(car, 0, len);
}
OutputStream os = socket.getOutputStream();
os.write("上传完毕!".getBytes());
bos.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
函数式接口:有且仅有一个抽象方法的接口
@FunctionalInterface // 检测接口是否是一个函数式接口
public interface MyFunction {
void method();
}
MyFunction my = () -> System.out.println("hello");
my.method(); // hello
使用 Lambda 优化日志案例(Lambda 延迟加载)
@FunctionalInterface // 检测接口是否是一个函数式接口
public interface MessageBuilder {
// 定义一个拼接消息的抽象方法, 返回被拼接的消息
String builderMessage();
}
String msg1 = "Hello";
String msg2 = "World";
String msg3 = "Java";
showLog(1, () -> msg1 + " " + msg2 + " " + msg3);
public static void showLog(int level, MessageBuilder mb) {
if(level == 1) { // 只有等级为 1 级, 才会执行拼接字符串
String msg = mb.builderMessage();
System.out.println(msg);
}
}
函数式接口作为方法的参数案例
startThread(() -> System.out.println(Thread.currentThread().getName() + "开启了"));
public static void startThread(Runnable run) {
new Thread(run).start(); // Thread-0开启了
}
函数式接口作为方法的返回值类型案例
String[] arr = { "Java", "JavaScript", "Node", "Vue" };
Arrays.sort(arr, getComparator());
System.out.println(Arrays.toString(arr)); // [Vue, Java, Node, JavaScript]
private static Comparator<String> getComparator() {
// return (o1, o2) -> o1.length() - o2.length();
return Comparator.comparingInt(String::length); // 比较字符串的长度
}
Supplier 接口:仅包含一个无参的方法:T get():用来获取一个泛型参数指定类型的对象数据
String str = getString(() -> "Java");
System.out.println(str); // Java
public static String getString(Supplier<String> sup) {
return sup.get();
}
Supplier 接口练习_求数组元素的最大值
int[] arr = { 16, 10, 79, 75, 29, 21, 30 };
int arrMax = getMax(() -> {
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
});
System.out.println(arrMax); // 79
public static int getMax(Supplier<Integer> sup) {
return sup.get();
}
Consumer 接口:与 Supplier 接口相反,它是消费数据
// 消费方式:直接输出字符串
method("Java", (name) -> System.out.println(name)); // Java
// 消费方式:将字符串进行反转
method("Java", (name) -> {
String revName = new StringBuilder(name).reverse().toString();
System.out.println(revName); // avaJ
});
public static void method(String name, Consumer<String> con) {
con.accept(name);
}
Consumer 接口的默认方法 andThen
// 连接两个 Consumer 接口
method("Java", (con1) -> {
// 消费方式:将字符串转为大写输出
System.out.println(con1.toUpperCase());
}, (con2) -> {
// 消费方式:将字符串转为小写输出
System.out.println(con2.toLowerCase());
});
public static void method(String s, Consumer<String> con1, Consumer<String> con2) {
con1.andThen(con2).accept(s); // 先执行 con1 再 con2
}
Consumer 接口练习_格式化打印信息
String[] arr = { "Java, 59", "JavaScript, 36", "Node, 19" };
printInfo(arr, (msg) -> {
// 消费方式:对字符串进行切割, 获取姓名
String name = msg.split(",")[0];
System.out.print("姓名:" + name + " ");
}, (msg) -> {
// 消费方式:对字符串进行切割, 获取年龄
String age = msg.split(",")[1];
System.out.println("年龄:" + age + "。");
});
/*
姓名:Java 年龄: 59。
姓名:JavaScript 年龄: 36。
姓名:Node 年龄: 19。
*/
public static void printInfo(String[] arr, Consumer<String> con1, Consumer<String> con2) {
for (String msg : arr) {
con1.andThen(con2).accept(msg);
}
}
Predicate 接口:对某种类型的数据进行判断,返回值是 boolean
String str = "Java";
boolean b = checkStr(str, (s) -> s.length() < 5);
System.out.println(b); // true
public static boolean checkStr(String s, Predicate<String> pre) {
return pre.test(s);
}
Predicate 接口的默认方法 and(需要两个条件都为 true 才 true)
boolean b = checkStr("Java", (s) -> s.length() < 4, (s) -> s.contains("a"));
System.out.println(b); // false
public static boolean checkStr(String s, Predicate<String> pre1, Predicate<String> pre2) {
return pre1.and(pre2).test(s);
}
Predicate 接口的默认方法 or (需要两个条件至少其中一个为 true 才 true
boolean b = checkStr("Java", (s) -> s.length() < 4, (s) -> s.contains("a"));
System.out.println(b); // true
public static boolean checkStr(String s, Predicate<String> pre1, Predicate<String> pre2) {
return pre1.or(pre2).test(s);
}
Predicate 接口的默认方法 negate(需要条件为假才 true)
boolean b = checkStr("Java", (s) -> s.length() > 4);
System.out.println(b); // true
public static boolean checkStr(String s, Predicate<String> pre) {
return pre.negate().test(s);
}
Predicate 接口练习_集合信息筛选(姓名为 4 个字,性别为女的数据)
String[] arr = { "迪丽热巴,女", "古力娜扎,女", "胡歌,男", "赵丽颖,女", "周杰伦,男"};
ArrayList<String> list = filter(arr, (s) -> {
String name = s.split(",")[0];
return name.length() == 4;
}, (s) -> {
String sex = s.split(",")[1];
return "女".equals(sex);
});
System.out.println(list); // [迪丽热巴,女, 古力娜扎,女]
public static ArrayList<String> filter(String[] arr, Predicate<String> pre1, Predicate<String> pre2) {
ArrayList<String> list = new ArrayList<>();
for (String s : arr) {
boolean b = pre1.and(pre2).test(s);
if(b) {
list.add(s);
}
}
return list;
}
Function
change("1230", (s) -> Integer.parseInt(s));
// 前面的参数是要转换的类型, 后面的参数是转换成什么样的类型
public static void change(String s, Function<String, Integer> fun) {
Integer in = fun.apply(s);
System.out.println(in); // 1230
}
Function 接口的默认方法 andThen:用来组合操作
String str = change("1230", (s) -> Integer.parseInt(s) + 10, (s) -> s.toString());
System.out.println(str); // 1240
// 把字符串转为 Integer 再加一个数 最后再转换为字符串
public static String change(String s, Function<String, Integer> fun1, Function<Integer, String> fun2) {
return fun1.andThen(fun2).apply(s);
}
Function生成定长字符串(泛型的第一个参数是传入的参数类型,第二个参数是返回的参数类型)
Function<Integer, String> randStrFunc = l -> {
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
StringBuilder str = new StringBuilder();
Random random = new Random();
for(int i = 0; i < l; i++) {
int position = random.nextInt(chars.length());
str.append(chars.charAt(position));
}
return String.valueOf(str);
};
String str = randStrFunc.apply(4);
System.out.println(str);
Stream 流属于管道流,只能消费一次,第一个流调用完毕之后就会流到下一个 Stream 上,之前那个流就不能再使用了
使用 Stream 流的方式遍历集合,对集合中的数据进行过滤再打印
Stream常用方法
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张三丰");
list.add("张东");
list.stream().filter(name -> name.startsWith("张"))
.filter(name -> name.length() == 3)
.forEach(name -> System.out.print(name + " ")); // 张无忌 张三丰
获取流(java.util.stream.Stream 是 Java 8 新加入的最常用的流接口,不是函数式接口)
// 方式一:集合转换为流对象
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
Set<String> set = new HashSet<>();
Stream<String> stream2 = set.stream();
Map<String, String> map = new HashMap<>();
Set<String> keySet = map.keySet();
Stream<String> stream3 = keySet.stream();
Collection<String> values = map.values();
Stream<String> stream4 = values.stream();
Set<Map.Entry<String, String>> entries = map.entrySet();
Stream<Map.Entry<String, String>> stream5 = entries.stream();
// 方式二:数组转换为流对象 Stream.of(...)
Stream<Integer> stream6 = Stream.of(1, 2, 3, 4, 5);
// 可变参数, 可以传数组
Integer[] arr = { 1, 2, 3, 4, 5 };
Stream<Integer> stream7 = Stream.of(arr);
forEach 方法
Stream<String> stream = Stream.of("Java", "JavaScript", "Node", "Vue");
stream.forEach((language) -> System.out.print(language + " ")); // Java JavaScript Node Vue
filter 方法(返回值还是一个流对象)
Stream<String> stream1 = Stream.of("Java", "JavaScript", "Node", "Vue");
Stream<String> stream2 = stream1.filter((language) -> language.startsWith("J"));
stream2.forEach((language) -> System.out.print(language + " ")); // Java JavaScript
映射:map 方法(一种数据类型转为另一种类型
Stream<String> stream1 = Stream.of("19", "29", "17", "9");
Stream<Integer> stream2 = stream1.map((s) -> Integer.parseInt(s));
stream2.forEach((num) -> System.out.print(num + " ")); // 19 29 17 9
统计个数:count
ArrayList<Integer> list = new ArrayList<>();
list.add(19);
list.add(89);
list.add(66);
list.add(51);
Stream<Integer> stream = list.stream();
System.out.println(stream.count()); // 4
取用前几个:limit 方法可以对流进行截取,只取用前几 n 个(延迟方法)
String[] arr = { "美羊羊", "喜羊羊", "沸羊羊", "小灰灰", "灰太狼" };
Stream<String> stream1 = Stream.of(arr);
// 使用 limit 对 stream 进行截取 只取前 3 个元素
Stream<String> stream2 = stream1.limit(3);
stream2.forEach(name -> System.out.print(name + " ")); // 美羊羊 喜羊羊 沸羊羊
跳过前几个:skip
String[] arr = { "美羊羊", "喜羊羊", "沸羊羊", "小灰灰", "灰太狼" };
Stream<String> stream1 = Stream.of(arr);
// 使用 limit 对 stream 进行截取 只取前 3 个元素
Stream<String> stream2 = stream1.skip(3);
stream2.forEach(name -> System.out.print(name + " ")); // 小灰灰 灰太狼
组合:concat:将两个流合并为一个流
String[] arr1 = { "美羊羊", "喜羊羊", "沸羊羊", "小灰灰", "灰太狼" };
String[] arr2 = { "Java", "JavaScript" };
Stream<String> stream1 = Stream.of(arr1);
Stream<String> stream2 = Stream.of(arr2);
Stream<String> stream3 = Stream.concat(stream2, stream1);
stream3.forEach(name -> System.out.print(name + " ")); // Java JavaScript 美羊羊 喜羊羊 沸羊羊 小灰灰 灰太狼
通过对象名引用成员方法
public class MethodRef {
public void printUpperCaseStr(String str) {
System.out.println(str.toUpperCase()); // JAVASCRIPT
}
}
// 对象名引用成员方法
MethodRef mr = new MethodRef();
printStr(mr::printUpperCaseStr);
public static void printStr(PrintTable pt) {
pt.print("JavaScript");
}}
通过类名引用静态成员方法
@FunctionalInterface
public interface Calculate {
int calcAbs(int num);
}
int num = method(-10, Math::abs);
System.out.println(num);
public static int method(int num, Calculate c) {
return c.calcAbs(num);
}
通过 super 引用父类的成员变量
public interface Greet {
void greet();
}
// 父类
public class Human {
public void sayHello() {
System.out.println("Hello, 我是 Human!"); // Hello, 我是 Human!
}
}
public class Man extends Human {
public void sayHello() {
System.out.println("Hello, 我是 man!");
}
public void method(Greet g) {
g.greet();
}
public void show() {
method(super::sayHello); // 引用父类的 sayHello
}
}
new Man().show();
通过 this 引用本类的成员方法
@FunctionalInterface
public interface Richer {
void buy();
}
public class Husband {
public void buyHouse() {
System.out.println("在北京二环内买一套四合院!"); // 在北京二环内买一套四合院!
}
public void marry(Richer r) {
r.buy();
}
public void soHappy() {
marry(this::buyHouse);
}
public static void main(String[] args) {
new Husband().soHappy();
}
}
类的构造器引用
@FunctionalInterface
public interface PersonBuilder {
// 根据传递的姓名,创建 Person 对象返回
Person builderPerson(String name);
}
public static void printName(String name, PersonBuilder pb) {
Person p = pb.builderPerson(name);
System.out.println(p.getName()); // 迪丽热巴
}
printName("迪丽热巴", Person::new);
数组的构造器引用
@FunctionalInterface
public interface ArrayBuilder {
int[] builderArray(int length);
}
public static int[] createArray(int length, ArrayBuilder ab) {
return ab.builderArray(length);
}
int[] arr = createArray(5, int[]::new);
System.out.println(Arrays.toString(arr)); // [0, 0, 0, 0, 0]
System.out.println(arr.length); // 5
框架:半成品软件。可以在框架的基础上进行软件开发,简化代码
反射:在运行时动态访问类与对象的技术
好处
反射的核心类
// 利用带参构造方法创建对象
Constructor constr = Class.forName("...").getConstructor(new Class[]{Integer.class, String.class...});
类 对象 = (类) constr.newInstance(new Object[]{100,"李磊"...);
Class employeeClass = Class.forName("com.imooc.reflect.entity.Employee");
// 得到方法对象,updateSalary方法名称,后面是为了解决重载的问题
Method updateSalaryMethod = employeeClass.getMethod("updateSalary" , new Class[]{ Float.class});
// 第一个参数传入employee对象,第二个参数传入对应的参数
(返回的是employee对象)Employee employee1 = (Employee)updateSalaryMethod.invoke(employee,new Object[]{1000f});
// 将Employee加载到JVM中
Class employeeClass = Class.forName("com.imooc.reflect.entity.Employee");
// 实例化对象
Constructor constructor = employeeClass.getConstructor(new Class[]{
Integer.class,String.class,Float.class,String.class
});
Employee employee = (Employee) constructor.newInstance(new Object[]{
100,"李磊",3000f,"研发部"
});
// 拿到属性ename
Field enameField = employeeClass.getField("ename");
enameField.set(employee,"李雷"); // 设置值, 第一个参数传入对象, 第二个参数传入设置的值
String ename = (String)enameField.get(employee); // 传入对象
getDeclared系列方法(可以获取被private修饰的构造方法、成员方法、成员变量)
Class employeeClass = Class.forName("com.imooc.reflect.entity.Employee");
Constructor constructor = employeeClass.getConstructor(new Class[]{
Integer.class, String.class, Float.class, String.class
});
Employee employee = (Employee) constructor.newInstance(new Object[]{
100, "李磊", 3000f, "研发部"
});
//获取当前类所有成员变量
Field[] fields = employeeClass.getDeclaredFields();
for(Field field : fields){
if(field.getModifiers() == 1){ //pubilc修饰
Object val = field.get(employee);
System.out.println(field.getName() + ":" + val);
} else if(field.getModifiers() == 2){ //private修饰
if(field.getName().equals("dname")) {
field.setAccessible(true); // 允许修改被private修饰的成员变量dname
field.set(employee, "开发部");
}
// 方法的get后面以大写开头,所以有字符串截取 substring
String methodName = "get" + field.getName().substring(0,1).toUpperCase()
+ field.getName().substring(1);
Method getMethod = employeeClass.getMethod(methodName);
Object ret = getMethod.invoke(employee);
System.out.println(field.getName() + ":" + ret);
}
}
获取 Class 对象的方式
Class<?> cls1 = Class.forName("com.bright.domain.Person");
System.out.println(cls1);
Class<Person> cls2 = Person.class;
System.out.println(cls2);
Person p = new Person();
Class<? extends Person> cls3 = p.getClass();
System.out.println(cls3);
System.out.println(cls1 == cls2); // true
System.out.println(cls1 == cls3); // true
结论:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的 Class 对象
获取功能:
// 获取 Person 类的字节码文件
Class<Person> cls = Person.class;
// 获取所有的成员变量们(只能获取被 public 修饰的)
Field[] fields = cls.getFields();
System.out.println(Arrays.toString(fields)); // []
// 获取成员变量 sex
Field sex = cls.getField("sex");
Person p = new Person();
// 给 sex 设置值
sex.set(p, "男");
Object value = sex.get(p);
System.out.println(value); // 男
// 获取所有的成员变量, 不考虑修饰符
Field f2 = cls.getDeclaredField("name");
// 为了解决 IllegalAccessException, 下面的暴力反射
f2.setAccessible(true);
System.out.println(f2.get(p)); // null
System.out.println(f2); // private java.lang.String com.bright.domain.Person.name
Class<Person> cls = Person.class;
Method eat = cls.getMethod("eat", String.class);
Person p = new Person();
eat.invoke(p, "饭"); // 吃饭了
Method[] methods = cls.getMethods();
for (Method method : methods) {
System.out.println(method);
method.setAccessible(true); // 也支持暴力反射
System.out.println(method.getName()); // 获取方法名称
}
案例
// 配置 pro.properties 文件(只需要改动配置文件即可)
className=com.bright.domain.Student
methodName=sleep
// 可以执行任意类和方法
// 加载配置文件
Properties pro = new Properties();
ClassLoader classLoader = ReflectTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("pro.properties");
pro.load(is);
String className = pro.getProperty("className");
String methodName = pro.getProperty("methodName");
// 加载该类进内存
Class<?> cls = Class.forName(className);
Object obj = cls.getDeclaredConstructor().newInstance();
Method method = cls.getMethod(methodName);
method.invoke(obj);
I18Na案例
language=com.bright.i18n.Zhcn
// 创建I18N接口
public interface I18N {
String say();
}
// 实现类
public class Zhcn implements I18N {
@Override
public String say() {
return "生命不息奋斗不止";
}
}
// 使用
@Test
public static void say() {
Properties prop = new Properties();
String configPath = Application.class.getResource("/config.properties").getPath();
try {
configPath = URLDecoder.decode(configPath, "UTF-8");
// 加载配置文件
prop.load(new FileInputStream(configPath));
String language = prop.getProperty("language");
I18N i18n = (I18N)Class.forName(language).getDeclaredConstructor().newInstance();
System.out.println(i18n.say());
} catch (Exception e) {
e.printStackTrace();
}
}
概念:说明程序的,给计算机看的
作用分类
JDK 内置注解
自定义注解
格式:元注解 @interface 注解名称()
本质:注解就是一个接口,该接口默认继承 Annotation 接口
属性:接口中可以定义的成员方法(抽象方法)
要求:属性的返回值类型:基本数据类型,String,枚举,注解,以上类型的数组(void 和自定义类不行)
定义了属性,在使用时需要给属性赋值
@SuppressWarnings("all")
public @interface MyAnno {
int value();
String name() default "JavaScript"; // 可以不赋值取默认值
String[] strs();
}
@MyAnno(value = 15, strs = {"Java", "JavaScript"})
public class Worker {
}
元注解:用于描述注解的注解
@Target(value = {ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) // 表示该注解只能作用于类上
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MyAnno1 {
}
解析注解
@Target(ElementType.TYPE) // 作用在类上
@Retention(RetentionPolicy.RUNTIME) // 作用在运行期阶段
public @interface Pro {
String className();
String methodName();
}
// 测试的类(注解中传递的全限定类名跟方法
public class Test01 {
public void show() {
System.out.println("Test01...show...");
}
}
// 注解解析并执行方法
// 解析注解
Class<ReflectTest> cls1 = ReflectTest.class;
// 获取注解对象
Pro an = cls1.getAnnotation(Pro.class); // 在内存中生成了一个该注解接口的子类实现对象
// 调用注解对象中定义的抽象方法, 获取返回值
String className = an.className();
String methodName = an.methodName();
System.out.println(className + "." + methodName); // com.bright.annotation.show
// 加载类进内存
Class<?> cls2 = Class.forName(className);
// 创建对象
Object obj = cls2.getDeclaredConstructor().newInstance();
// 获取方法对象
Method method = cls2.getMethod(methodName);
method.invoke(obj);
注解_简单的测试框架
// 检测要出错的类
public class Calculator {
// 加法
@Check
public void add() {
System.out.println("1 + 0" + (1 + 0));
}
// 减法
@Check
public void sub() {
int a = 5 / 0;
System.out.println("1 - 0" + (1 - 0));
}
// 乘法
@Check
public void mul() {
String str = null;
System.out.println(str.equals("1"));
System.out.println("1 * 0" + (1 * 0));
}
// 除法
@Check
public void div() {
System.out.println("1 / 0" + (1 / 0));
}
@Check
public void show() {
System.out.println("永无bug...");
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Check {
}
// 测试框架
// 创建计算器对象
Calculator calc = new Calculator();
// 获取字节码文件对象
Class<? extends Calculator> cls = calc.getClass();
// 获取计算器中所有的方法
Method[] methods = cls.getMethods();
int num = 0; // 出现异常出现的次数
BufferedWriter bw = new BufferedWriter(new FileWriter(".\\bug.txt"));
for (Method method : methods) {
if(method.isAnnotationPresent(Check.class)) { // 判断每一个方法是否有 Check 注解修饰
try {
method.invoke(calc);
} catch (Exception e) {
// 铺货异常, 记录到文件中
num++;
bw.write(method.getName() + "方法出异常了...");
bw.newLine();
bw.write("异常的名称:" + e.getCause().getClass().getSimpleName());
bw.newLine();
bw.write("异常的原因:" + e.getCause().getMessage());
bw.newLine();
bw.write("---------------------------");
bw.newLine();
}
}
}
bw.write("本次测试一共出现" + num + "次异常");
bw.flush();
bw.close();
优点
缺点
使用场景
饿汉式的代码实现(在类加载时就创建实例)
// 饿汉式:创建对象实例的时候直接初始化
public class SingletonOne {
// 创建类中私有构造
private SingletonOne(){}
// 创建该类型的私有静态实例
private static SingletonOne instance = new SingletonOne();
// 创建公有静态方法返回静态实例对象
public static SingletonOne getInstance(){
return instance;
}
}
测试
SingletonOne one = SingletonOne.getInstance();
SingletonOne two = SingletonOne.getInstance();
System.out.println(one == two); // true
懒汉式的代码实现(以时间换空间,第一次使用时进行初始化)
// 懒汉式:类中实例对象创建时并不直接初始化,直到第一次调用 getInstance 时才完成初始化
public class SingletonTwo {
// 创建私有构造方法
private SingletonTwo(){}
// 创建静态的该类实例对象
private static SingletonTwo instance = null;
// 创建公有静态的该类实例对象
public static SingletonTwo getInstance() {
if(instance == null) {
instance = new SingletonTwo();
}
return instance;
}
}
测试
SingletonTwo one = SingletonTwo.getInstance();
SingletonTwo two = SingletonTwo.getInstance();
System.out.println(one == two); // true
介绍
i18n国际化的简单应用
// 接口
public interface I18N {
String getTitle();
}
// 工厂类
public class I18NFactory {
// 一般为静态方法, 返回指定的实现类
public static I18N getI18NOnject(String area){
if(area.equals("china")) {
return new Chinese();
} else if(area.equals("spain")) {
return new Spainish();
} else if(area.equals("italy")) {
return new Italian();
} else {
return null;
}
}
}
// 实现类等
public class Chinese implements I18N {
public String getTitle() {
return "人事管理系统";
}
}
// 使用工厂来创建实现类
I18N i18N = new I18NFactory().getI18NOnject("italy");
System.out.println(i18N.getTitle());
使用工厂模式模拟多端应用切换
// Device接口
public interface Device {
String getIndex();
String getDescription();
}
// 两个实现类
public class DesktopDevice implements Device {
public String getIndex() {
return "/desktop/index.html";
}
public String getDescription() {
return "/desktop/desc.html";
}
}
public class MobileDevice implements Device {
public String getIndex() {
return "/mobile/index.html";
}
public String getDescription() {
return "/mobile/desc.html";
}
}
// 工厂类
public class DeviceFactory {
public static Device getDevice(HttpServletRequest request) {
String userAgent = request.getHeader("user-agent");
System.out.println(userAgent);
if(userAgent.indexOf("Windows NT") != -1) {
return new DesktopDevice();
} else if(userAgent.indexOf("iPhone") != -1 || userAgent.indexOf("Android") != -1) {
return new MobileDevice();
} else {
return null;
}
}
}
// 使用
Device de = DeviceFactory.getDevice(request);
request.getRequestDispatcher(de.getIndex()).forward(request, response);