子类可以继承父类中有的成员变量和成员方法
好处 : 提高代码的复用性
关键字 extends
构造方法不会被继承,子类要写自己的构造方法
父类中私有的成员可以被继承,被继承的成员依然是只能被父类访问;
变量或者方法被调用时,系统会先在本类中寻找,本类中没有就会访问父类中的变量
this 调用本类成员变量
super 调用父类成员变量
```java
this 本类方法的引用,谁调用,this代表调用的处对象 this.method(); 相当于method
super 父类方法的引用, super.method();
```
this(); 调用本类构造方法
super(); 调用父类构造方法
tip
(1) this 和 super 只能写在构造方法的第一行
(2) 如果构造方法没有写明,构造方法第一行默认有一句super();
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jc797JP6-1631766334597)(3.继承的内存图.png)]
在内存中,创建子类对象时,会在子类对象内存空间先创建父类对象, 供子类对象使用
1.子类中的方法与父类中的一模一样(参数,返回值类型,方法名,修饰符)
2.重写后子类调用时有限使用子类自己的方法
3.这样使得子类即可以继承父类的方法又可以有自己特有的功能
1.继承必须有类的所属于父类的关系,子类属于父类才能继承
2.类只支持单继承,一个类只能有一个父类
3.类可以多层继承,一个类有父类,父类还可以有自己的父类,多层继承依然遵循继承的使用规则
1.方法体不是具体的
2.没有实例,不能创建对象
3.关键字 abstract
public abstract class 类名{
}
普通类能写的都能写
成员变量
构造方法
成员方法
抽象方法
public abstract 返回值类型 method();
只有方法的定义,没有方法体
1.抽象类中可以有抽象方法,也可以没有
2.有抽象方法的类必须是抽象类
3.抽象类不能创建对象
4.抽象类一定是一个父类,抽象类的子类要么重写抽象方法,要么也是抽象方法
5.抽象类不能创建对象,构造方法用来给子类创建对象调用
给子类提供抽象方法,子类可以通过重写方法定义具体的实现方式
基本数据类型 存的是数据值
不能再次被赋值,变量定义为常量 Math.PI
引用数据类型 存的是地址值
引用地址不可以改变,地址值
被final修饰的方法不能被重写
final和abstract排斥 private 和 abstract
被修饰的类不能被继承
作用: 被static修饰的成员变量属于类,被类的所有对象共享
调用: 对象.成员变量
类名.成员变量
推荐 类名调用 (static修饰的成员属于类,在内存中存放在静态方法区)
作用: static修饰成员方法,方便调用
调用: 对象.成员方法
类名.成员方法
推荐 类名调用
静态方法不能直接访问(间接创建对象)非静态成员,成员方法可以直接访问类的静态成员和非静态成员
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wK1jUcGT-1631766334603)(D:/java/%E7%AC%94%E8%AE%B0/se%E8%BF%9B%E9%98%B6/2.%E9%9D%99%E6%80%81%E7%9A%84%E5%86%85%E5%AD%98%E5%9B%BE.png)]
接口是一种规范
定义格式
public interface 接口名{
}
实现
public class 类名 implements{
}
1.抽象方法 public abstract 返回值 方法名(); (public abstract 可以省略)
2.常量 默认被public static final 修饰;
JDK8加入
3.默认方法 public default 返回值类型 方法名 (){ ( public 可以省略)
}
(JDK9加入
4.私有方法 私有成员方法,私有静态方法
1.常量默认修饰符 public static fianl
2.抽象方法默认修饰符 public abstract
3.接口不能创建对象
4.接口不能有构造方法
5,接口的实现类必须重写抽象方法或者实现类也是抽象类
作用: 定义一种规范
在在开发中定义好方法的返回值,方法名,参数
一种事务的多种形态
student 既是Student类也是Person类
java中 父类引用指向子类对象
必须有继承extends 或者 实现 implements 关系
成员变量 编译看左边,执行看左边
成员方法 编译看左边,执行看右边
缺点:
不能使用子类特有的成员(成员变量和成员方法)
优点:
提高了程序的扩展性
父类作为方法的参数,可以传入子类对象调用子类重写的方法
1.向上转型 (自动转换)
多态 父类引用指向子类对象
Animal a = new Cat();
2.向下转型 (强制转换)
cat c = (cat)a;
格式:
public class AAA{
int i = 10;
class BBB{
int i=20;
public void method(){
int i =30;
System.out.println(i);//30
System.out.println(this.i);//20
System.out.println(AAA.this.i);30
}
}
}
创建内部类对象
外部类.内部类 对象名 = new 外部类().new 内部类
AAA.BBB ab = new AAA().new BBB();
内部类访问外部类成员变量
System.out.println(i);//30
System.out.println(this.i);//20
System.out.println(AAA.this.i);30
public class AAA{
public void method(){
class BBB{
int a=10;
}
}
}
用处不大
Person p=new Person{
public void method(){
重写父类方法
}
};
方法的参数是抽象方法或者接口,可以通过匿名内部类的方式创建抽象方法或者接口的子类
可以简化代码
内部类文件名 外部类名$内部类名.class
匿名内部类,文件名 外部类$数字.class
public > protected > 默认 > private
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NOAltEKT-1631766334606)(四种权限修饰符.png)]
static{
语句体
}
类被加载的时候自动执行,只执行一次
用于静态变量的初始化
{
语句体
}
每次创建对象的时候被自动调用
作用: 构造方法有相同语句体的时候可以放在构造代码块
写在方法中 ,用来约束变量
作用: 方法中用不到的变量通过{ }可以在方法执行完之前释放内存,
默认 object中 this == o( 参数 ) 比较的是地址值
在对象所属类中重写之后比较对象的属性
//学生类
@Override
//逻辑严谨
public boolean equals(Object o) {
if(this==o) return true;
if(o==null||getClass()!=o.getClass()) return false;
Student stu = (Student)o;
if(age!=stu.age) return false;
return name!=null?name.equals(stu.name):stu.name==null;
}
默认 object中 输出 类型.类名+@+十六进制地址值
重写后输出自定义内容
3.native 本地的 关键词
native修饰的方法没有方法体,调用了其他语言写的调用系统资源的代码
new Date(); 获取当前时间
new Date(long time) 设置时间 从基准时间开始
计算机中的基准时间 1970年 1 月1 日
方法名 | 作用 |
---|---|
long getTime( ) | 返回基准时间到调用对象的毫秒值 |
after( ); | 判断是调用对象否在参数 |
before( ); | 判断 |
new SmipleDateFormat( String pattern );
方法名 | 作用 |
---|---|
String format( Date date); | 把日期类转换为String |
Date parse(String source) | 把String解析为 Date |
Calendar.getInstence();
方法名 | 作用 |
---|---|
get( int field ); | 根据字段返回对应的值 |
set(int field ); | 设置字段的值 |
add(int field,int values); | 字段加减操作 |
private修饰构造方法,不能创建对象,
所有的成员都用static修饰,用类名调用
sort(); 排序
toString(); 把数组输出为字符串
常用方法
exit(int status); 退出虚拟机
currentTimeMillis() 获取当前系统时间
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
复制数组 src 源数组 srcPos 源数组复制的起始位置, dest 目标数组 destPos 目标数组的起始位置, length 复制的长度
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z7pDzVjx-1631766334610)(D:\java\笔记\se进阶\集合体系.png)]
方法名 | 用法 |
---|---|
public boolean add(E e); | 添加元素 |
public boolean remove(E e); | 删除元素 |
public boolean contains(E e); | 判断集合是否包含参数 |
public int size(); | 返回集合的长度 |
Collection系的集合可以使用集合对象调用Iterator方法
hasNext(); 判断是否还有下一个元素
Next(); 返回下一个元素;
while(iterator.hasNext()){
iterator.Next();
}
原因 : 使用迭代器遍历数组时,如果在遍历中使用中集合删除或增加元素,就会引起并发修改异常
措施 : 使用iterator的remove方法
底层是迭代器
格式
for(数据类型 变量名:集合名){
此时的变量就代表集合中的数据
}
1.作用于类
定义在类名的后边 , 具体确定类型是在创建对象的时候
2.作用于方法
定义在返回值之前, 具体确定类型是在调用方法的时候
3.作用于接口
定义在接口的接口名之后,继承类可以确定具体.类型,或者继承类也定义相同的泛型;继承类的实现类创建对象时确定具体类型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T7WcNKZP-1631766334613)(D:\java\黑马java\java基础进阶\day05\笔记\3.栈结构.png)]
栈 只有一个开口,先进后出
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iZg0QnWF-1631766334614)(D:\java\黑马java\java基础进阶\day05\笔记\4.队列结构.png)]
队列 有两个开口,先进先出
查找快,增删慢
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DXdI32JN-1631766334616)(D:\java\黑马java\java基础进阶\day05\笔记\5.数组结构.png)]
增删快,查找慢
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fQgclnCG-1631766334617)(D:\java\黑马java\java基础进阶\day05\笔记\6.链表结构.png)]
方法名 | 用法 |
---|---|
boolean add(int index , E e ); | 根据索引添加元素(插队) |
E get( int index ) | 获取索引位置的元素 |
E remove(int index) | 根据索引删除元素 |
E set(int index, E e) | 设置索引位置的元素 |
继承自Collection 和 List 的方法
底层用数组实现,元素增删慢,查找快
特有方法,对首尾元素进行添加获取的方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rEBBukfc-1631766334619)(D:\java\笔记\se进阶\Snipaste_2021-09-03_20-45-49.png)]
1.格式
public void sum (int ... a){
}
2.注意事项
(1)传入实参的时候可以传任意个数的元素,也可以传入数组;
(2)可变参数的底层是数组, 在方法中可以把a当做一个数组来使用
(3) 形参列表中只能定义一个可变参数,多个参数的情况可变参数要定义在参数列表最后一个;
Collections是集合的工具类,所有方法都是静态方法
方法 | 说明 |
---|---|
static void shuffle(List list) | 随机打乱集合元素的顺序 |
static void sort(List list) | 集合的排序(从小到大) |
static void addAll(Collection<> coll, T… t ) | 给集合一次添加多个元素 |
static void sort(List list,Comparator ) | 按照指定的方式排序 |
import java.util.ArrayList;
import java.util.Collections;
public class Demo01 {
public static void main(String[] args) {
//Collections的方法
//创建两个集合
ArrayList<Integer> list1 = new ArrayList<>();
list1.add(123);
list1.add(456);
list1.add(789);
list1.add(100);
System.out.println("初始:" + list1);
//static void shuffle(List list)
//随机打乱集合元素的顺序
Collections.shuffle(list1);
System.out.println("打乱" + list1);
//static void sort(List list)
//集合的排序(从小到大)
Collections.sort(list1);
System.out.println("排序" + list1);
//static void addAll(Collection<> coll, T... t )
//给集合一次添加多个元素
Collections.addAll(list1,20,30,40);
System.out.println("添加" + list1);
}
}
参数:
Integer o1 : 代表要比较的元素
Integer o2 : 代表已经比较完的元素
返回值:
int:
如果返回值是正数,代表要把o1放在o2的后面
如果返回值是负数,代表要把o1放在o2的前面
如果返回值是零,代表要把o1和o2位置不变,默认o1在后面
记住结论:
o2-o1就是从大到小 o1-o2就是从小到大
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class Demo02 {
public static void main(String[] args) {
//创建集合
ArrayList<Integer> list1 = new ArrayList<>();
list1.add(123);
list1.add(456);
list1.add(789);
list1.add(100);
//匿名内部类
Comparator<Integer> c = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
};
//排序
Collections.sort(list1,c);
System.out.println(list1); //[789, 456, 123, 100]
}
}
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class Demo05 {
public static void main(String[] args) {
//创建集合对象
ArrayList<String> list = new ArrayList<>();
//添加元素
list.add("anc");
list.add("abc");
list.add("cba");
list.add("wcba");
list.add("nba");
list.add("mba");
//创建比较器
Comparator<String> c = new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
//int i = o1.compareTo(o2); //从小到大
int i2 = o2.compareTo(o1); //从大到小
return i2;
}
};
//排序
Collections.sort(list,c);
//打印
System.out.println(list);
}
}
hashSet没有索引,元素存取无序,元素不能重复
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r2oX3GGT-1631766334620)(D:/java/%E9%BB%91%E9%A9%ACjava/java%E5%9F%BA%E7%A1%80%E8%BF%9B%E9%98%B6/day06%E9%9B%86%E5%90%882/%E7%AC%94%E8%AE%B0/2.%E5%93%88%E5%B8%8C%E8%A1%A8%E7%BB%93%E6%9E%84.png)]
每个对象都可以调用hashCode()方法,获取这个对象的哈希值
哈希值是一个整数
如果哈希值不同,说明两个对象一定不同
如果哈希值相同,两个对象不一定相同
public class Demo02 {
public static void main(String[] args) {
String s1 = "ab";
String s2 = "abc";
String s3 = "Aa";
String s4 = "BB";
System.out.println(s1.hashCode()); //3105
System.out.println(s2.hashCode()); //96354
System.out.println(s3.hashCode()); //2112
System.out.println(s4.hashCode()); //2112
//底层计算哈希值的时候,想要给每个不同的对象生成一个不同的整数哈希值,所以搞比较复杂的计算方法
/*
int hash = 0;
循环遍历字符串{
hash = hash * 31 + 获取每个字符;
}
hash的值就是这个字符串的hash结果
*/
}
}
HashSet在添加元素的时候,会调用元素的hashCode()和equals()方法
String s0 = "abc";
String s1 = "abc";
String s2 = "Aa";
String s3 = "BB";
String s4 = new String("BB");
p.hash == this.hash && (p.key == this.key || (key != null && key.equals(k)))
先判断两个元素的哈希值
如果两个元素哈希值不同,就直接知道两个元素不重复!
如果两个元素哈希值相同,就去判断两个元素的地址值
如果两个元素地址值相同,就直接知道两个元素重复!
如果两个元素地址值不同,就判断两个元素的内容
如果两个元素内容相同,就重复!
如果两个元素内容不同,就不重复!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jC5cI47Z-1631766334621)(D:/java/%E9%BB%91%E9%A9%ACjava/java%E5%9F%BA%E7%A1%80%E8%BF%9B%E9%98%B6/day06%E9%9B%86%E5%90%882/%E7%AC%94%E8%AE%B0/3.HashSet%E7%9A%84%E5%AD%98%E5%82%A8.png)]
如果要存储自定义类型,需要重写hashCode()和equals()方法
package com.itheima02;
import java.util.Objects;
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
//重写方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
package com.itheima02;
import java.util.HashSet;
public class Demo04 {
public static void main(String[] args) {
//创建集合
HashSet<Student> set = new HashSet<>();
//创建学生对象
Student s1 = new Student("马冬梅",23);
Student s2 = new Student("马冬梅",23);
Student s3 = new Student("夏洛",28);
//添加到集合
set.add(s1);
set.add(s2);
set.add(s3);
//打印
System.out.println(set);
}
}
元素没有索引,元素不能重复,元素存取有序
package com.itheima02;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
public class Demo06 {
public static void main(String[] args) {
//创建集合
LinkedHashSet<String> set = new LinkedHashSet<>();
set.add("123");
set.add("abc");
set.add("abc");
set.add("edf");
System.out.println(set); //[123, abc, edf]
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-30IY4Kk0-1631766334623)(D:/java/%E9%BB%91%E9%A9%ACjava/java%E5%9F%BA%E7%A1%80%E8%BF%9B%E9%98%B6/day06%E9%9B%86%E5%90%882/%E7%AC%94%E8%AE%B0/4.%E5%8D%95%E5%88%97%E9%9B%86%E5%90%88%E6%80%BB%E7%BB%93.png)]
Map集合称为双列集合,保存的是一对儿一对儿的数据,一对儿的数据也叫键值对
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ysZrc3aI-1631766334624)(D:/java/%E9%BB%91%E9%A9%ACjava/java%E5%9F%BA%E7%A1%80%E8%BF%9B%E9%98%B6/day06%E9%9B%86%E5%90%882/%E7%AC%94%E8%AE%B0/5.Map%E9%9B%86%E5%90%88%E4%BD%93%E7%B3%BB.png)]
方法 | 说明 |
---|---|
V put(K key, V value) | 添加键值对 |
V remove(Object key) | 根据键删除对应的键值对 |
V get(Object key) | 根据键获取值 |
boolean containsKey(Object key) | 判断是否包含某个键 |
boolean containsValue(Object value) | 判断是否包含某个值 |
int size() | 获取集合长度 |
import java.util.HashMap;
import java.util.Map;
public class Demo01 {
public static void main(String[] args) {
//Map双列集合
//创建Map集合
Map<String,String> map = new HashMap<>();
//V put(K key, V value)
//添加键值对
map.put("夏洛","秋雅");
map.put("贾乃亮","小璐");
map.put("王宝强","马蓉");
System.out.println(map); //{贾乃亮=小璐, 王宝强=马蓉, 夏洛=秋雅}
//V remove(Object key)
//根据键删除对应的键值对
map.remove("夏洛");
System.out.println(map); //{贾乃亮=小璐, 王宝强=马蓉}
//V get(Object key)
//根据键获取值
String s = map.get("贾乃亮");
System.out.println("值是" + s);
//boolean containsKey(Object key)
//判断是否包含某个键
boolean b1 = map.containsKey("王宝强");
System.out.println(b1);
//boolean containsValue(Object value)
//判断是否包含某个值
boolean b2 = map.containsValue("马蓉");
System.out.println(b2);
//int size()
//获取集合长度
int size = map.size();
System.out.println(size);
}
}
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Demo02 {
public static void main(String[] args) {
//创建Map集合
Map<String,String> map = new HashMap<>();
//V put(K key, V value)
//添加键值对
map.put("夏洛","秋雅");
map.put("贾乃亮","小璐");
map.put("王宝强","马蓉");
//keySet()
Set<String> set = map.keySet();
//增强for
for (String s : set) {
//map获取值
String v = map.get(s);
//打印
System.out.println(s+"-"+v);
}
}
}
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Demo03 {
public static void main(String[] args) {
//创建Map集合
Map<String,String> map = new HashMap<>();
//V put(K key, V value)
//添加键值对
map.put("夏洛","秋雅");
map.put("贾乃亮","小璐");
map.put("王宝强","马蓉");
//entrySet()
Set<Map.Entry<String, String>> set = map.entrySet();
//增强for
for (Map.Entry<String, String> entry : set) {
//获取键
String k = entry.getKey();
//获取值
String v = entry.getValue();
//打印
System.out.println(k + "-" + v);
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V5QZP5cT-1631766334625)(D:/java/%E9%BB%91%E9%A9%ACjava/java%E5%9F%BA%E7%A1%80%E8%BF%9B%E9%98%B6/day06%E9%9B%86%E5%90%882/%E7%AC%94%E8%AE%B0/6.Map%E9%9B%86%E5%90%88%E7%9A%84keySet%E9%81%8D%E5%8E%86.png)]
元素存取无序,元素键不能重复
元素存取有序,元素键不能重复
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
public class Demo04 {
public static void main(String[] args) {
//创建集合对象
HashMap<String,String> map1 = new HashMap<>();
map1.put("小璐","贾乃亮");
map1.put("秋雅","夏洛");
map1.put("马蓉","王宝强");
//如果键重复了,会用新的值覆盖旧的值
map1.put("马蓉","宋喆");
System.out.println(map1); //存取无序,元素键不能重复
LinkedHashMap<String,String> map2 = new LinkedHashMap<>();
map2.put("小璐","贾乃亮");
map2.put("秋雅","夏洛");
map2.put("马蓉","王宝强");
//如果键重复了,会用新的值覆盖旧的值
map2.put("马蓉","宋喆");
System.out.println(map2); //存取有序,元素键不能重复
}
}
输入一个字符串判断里面每个字符出现次数。
import java.util.LinkedHashMap;
import java.util.Scanner;
public class Demo01 {
public static void main(String[] args) {
//1.键盘输入一个字符串
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串:");
String s = sc.next();
//2.创建一个Map集合
LinkedHashMap<Character,Integer> map = new LinkedHashMap<>();
//3.遍历字符串
for (int i = 0; i < s.length(); i++) {
//4.获取字符串的每个字符
char ch = s.charAt(i);
//5.判断集合的键中是否包含字符
boolean b = map.containsKey(ch);
//6.如果不包含就添加字符次数是1
if(b==false){
map.put(ch,1);
}else {
//7.如果包含就获取原来的值+1再放回去
Integer count = map.get(ch);
map.put(ch,count+1);
}
}
//8.打印
System.out.println(map);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DRLvEfjY-1631766334626)(D:/java/%E9%BB%91%E9%A9%ACjava/java%E5%9F%BA%E7%A1%80%E8%BF%9B%E9%98%B6/day06%E9%9B%86%E5%90%882/%E7%AC%94%E8%AE%B0/7.%E6%96%97%E5%9C%B0%E4%B8%BB%E5%88%86%E6%9E%90.png)]
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
public class Demo02 {
public static void main(String[] args) {
//创建Map集合
Map<Integer,String> map = new LinkedHashMap<>();
//创建ArrayList集合
ArrayList<Integer> list = new ArrayList<>();
//创建花色数组
String[] color = {"♥","♣","♠","♦"};
//创建数字数组
String[] num = {"3","4","5","6","7","8","9","10","J","Q","K","A","2"};
//定义变量
int i = 1;
//遍历数组
for (String n : num) {
for (String c : color) {
//给集合添加
map.put(i,c+n);
list.add(i);
i++;
}
}
//单独添加王
map.put(53,"");
map.put(54,"");
list.add(53);
list.add(54);
//洗牌
Collections.shuffle(list);
//创建4个集合
ArrayList<Integer> list1 = new ArrayList<>();
ArrayList<Integer> list2 = new ArrayList<>();
ArrayList<Integer> list3 = new ArrayList<>();
ArrayList<Integer> list4 = new ArrayList<>();
//发牌
for (int j = 0; j < list.size(); j++) {
//获取元素
Integer a = list.get(j);
if(j>50){
list4.add(a);
}else if(j%3==0){
list1.add(a);
}else if(j%3==1){
list2.add(a);
}else{
list3.add(a);
}
}
//排序
Collections.sort(list1);
Collections.sort(list2);
Collections.sort(list3);
//看牌,拿数字去map里面找牌
method(list1,map,"旭旭宝宝");
method(list2,map,"车友车行");
method(list3,map,"卢本伟 ");
method(list4,map,"底牌 ");
}
//定义看牌方法
public static void method(ArrayList<Integer> list,Map<Integer,String> map,String name){
//遍历list集合
System.out.print(name + ":");
for (Integer a : list) {
//把每个数字当做键从map里面找值
String s = map.get(a);
//打印值
System.out.print(s+"");
}
System.out.println();
}
}
1).当我们的程序遇到无法处理的数据,使程序无法继续运行时,这种情况就叫"异常"
2).当JVM执行代码过程中,发生了异常情况,程序会被迫终止——这对于最终用户,非常不友好!!
1).Java中提供了一种"处理异常"的语法、机制,可以让代码产生异常时,可以跳过异常的代码,继续健康的运行下去
1).JVM执行到有异常的代码
2).JVM会识别出异常情况
3).JVM会到"类库"中查找"描述这种异常的异常类",并创建一个对象。
Java针对任何异常,都有对应的"异常类"
4).JVM会查看我们的代码是否希望"捕获"这个异常(catch(InputMismatchException e)),
a).如果没有catch(InputMismatchException e):向控制台打印异常类名 + 异常的位置,然后结束程序
b).如果有catch(InputMismatchException e):会将之前产生的异常对象传递到catch代码中,并执行catch中的代码,程序就不会死了。
Java类库中有很多的"异常类",每种异常类都描述了一种异常情况,这些异常类的体系结构:
Throwable(顶层异常类)
|–Error(严重的错误):指程序中不需要处理,也是无法处理的一些很严重的异常情况
类名后缀:XxxxxError
示例:
1).定义一个Student类:
public class Student {
int[] arr = new int[1024 * 1024];//4M空间
}
2).定义一个测试类:
public static void main(String[] args) {
//Error:程序不需要处理的非常严重的错误
ArrayList stuList = new ArrayList<>();
while (true){
stuList.add(new Student());
}
}
|–Exception(异常):
|–RuntimeException(运行时异常):比较普通的异常,如果程序员比较负责任,完全可以避免的异常情况。
1).NullPointerException:空指针异常
2).IndexOutOfBoundsException:索引越界异常(使用ArrayList索引超出范围时)
3).ArrayIndexOutOfBoundsException:数组索引越界异常(使用数组时,索引超出范围)
4).StringIndexOutOfBoundsException:字符串的索引越界异常(使用String时,例如:charAt(index)),索引超出范围)
5).InputMismatchException:Scanner接收到一个与预期不符的数据时,抛出的异常
…
|–除RuntimeException(及子类)以外的其它异常:非运行时异常,也叫:编译期异常。这种异常通常需要我们使用try…catch进行处理,如果不处理,编译不通过
1).ParseException:使用SimpleDateFormat的parse方法时,抛出的异常
…
try{
//写可能出现异常的代码
}catch(异常类名 变量名){
//如果try中出现了和"异常类名"一样的异常,
//将会执行此catch中的代码
}
注意:
1).如果try中多行代码,只要某一行代码出现异常,try中的后续代码将全部被跳过
2).catch(异常类名)中的"异常类名"必须要和try中产生的异常"一致",否则无法"捕获"
3).catch中的"异常类型"尽量"精确",尽量不要使用"父类异常类型"
但是,建议以下这种写法:
try {
Scanner sc = new Scanner(System.in);
System.out.println(“请输入年龄:”);
try {//try:尝试
int age = sc.nextInt();//出现异常,下面代码将不会被执行
System.out.println(“你的年龄是:” + age);//上面的代码出异常,这一行不会被执行
} catch (InputMismatchException e) {//catch:捕获异常对象
System.out.println(“你的输入有误!但是程序没有死,仍然可以继续健康的运行下去”);
}
System.out.println(“后续代码…”);
} catch (Exception e) {
System.out.println(“出现未知异常,请联系系统管理员!”);
在try中如果可能产生多种异常,可以使用以下语句:
try{
…
}catch(异常类型1 变量名){
…
}catch(异常类型2 变量名){
…
}
注意:
1).多个catch()里面的"异常类型"通常是"平级关系",都是子类。
2).多个catch()里面的"异常类型"可以是"子父关系",但"父类的异常类型"必须声明在最后面
3).JDK7开始,又有一种新的写法:【不常见】【当多种异常,都使用一种处理方式,可以使用这个语法】
try{
…
}catch(ArrayIndexOutOfBoundsException | NullPointerException | 其它异常类型… 变量名){
//注意:多个异常类型是"或"的关系,所以这里不能出现"父类"类型
//try中只要出现这几种异常的一种,都会执行此catch,在这里如果需要,可以判断具体是哪种类型,比较麻烦
}
try{
…
}catch(异常类型 变量名){
//如果try中出现"异常类型"的异常,会执行这里
}finally{
//无论try中是否出现异常,都会被执行的代码
//通常做一些"资源释放"的操作,当try和catch中都执行完毕,需要释放资源时,可以将代码写在这里
//这里不要修改try/catch使用的变量,return也不是finally中修改的值。
//可以不return 值[建议],也可以return 值[不建议]
}
应用场景:通常用在某个"方法"中,而且当try/catch中有return语句时,在return之前,无论是否出现异常,
在return之前都需要被执行的代码,可以写在finally中。
try、catch、finally三个关键字有以下几种组合方式:
1).try…catch
2).try…catch…catch
3).try…catch…finally
4).try…catch…catch…finally
5).try…finally(try中如果出现异常,会执行finally,但由于没有catch,所以程序仍然会终止)
throws:声明抛出异常,异常的处理方式之一(另一种是try/catch)
它是一个关键字,在"方法"的声明上处理异常,
表示:告诉JVM,如果此方法中产生了声明的异常,让JVM将异常对象抛给"调用的代码"(谁调用我,我就抛给谁)
注意:
如果throws抛出的是:
a).RuntimeException(或者它的子类对象):调用的代码不用强制try...catch,可以编译通过,
但是,如果出现异常,程序仍然是死掉
b).编译期异常(除Runtime及其子类以外的异常):调用的代码必须try...catch或者继续throws抛出这个异常,否则编译错误!
//throws 处理后异常程序出现异常后中断执行
//try catch 方法处理后可以按照自己的逻辑在catch中对异常进行处理
1.创建异常
2.抛出异常 1+2两个步骤叫做产生了异常
3.处理异常
- 声明throws抛出异常
- try/catch捕获异常
throw关键字:用在方法内部,用于:抛出一个异常对象
注意:
1).如果throw抛出的是"运行时异常",可以不处理异常,即不声明抛出也不try…catch捕获
2).如果throw抛出的是"非运行时异常",方法必须处理,声明抛出或者try…catch捕获
1.自定义类,继承自Exception或者是它的某个子类
注意:自定义类 extends Exception :自定义类就是:编译期异常
自定义类 extends RuntimeException(子类):自定义类就是:运行时异常
2.在某个类的方法中
throw new 自定义异常对象(“异常信息”)
========================================================================
//异常对象的三个常用方法
//1.获取异常信息
System.out.println(“异常信息:” +
e.getMessage());
//2.toString()
System.out.println(“toString:” +
e.toString());//异常类名 + 异常信息
//3.打印异常的完整信息
e.printStackTrace();//红色的异常类名 + 异常信息 + 异常的位置
多线程
进程是正在执行的程序
线程是程序可以独立执行的单元
进程:是操作系统中的概念,指一个独立运行的程序就是一个"进程"
线程:由"进程"创建的,与主进程(即理解为主线程)“同时执行”,提高程序的效率,同时做多件事情。
java程序中,main方法执行在主线程中。
指我们的程序可以将一部分代码剥离出去,与主程序"同时执行"——这样可以使我们的程序"同时"做多件事情,提高程序的效率!!
1).之前我们编写的程序都是"单线程"的程序,代码从main()开始,后面的代码都要等待前面的代码执行完毕,然后才能被执行。
2).并发:一颗CPU,两个线程
并行:两颗CPU,两个线程,每个线程占用一颗CPU。
上述是在"硬件"层面上的"并发"与"并行"
1).自定类 extends Thread
2).重写run()方法。线程中要做的事情写在run()方法
即定义这个线程要执行的代码
3).启动线程
创建一个线程类对象:自定义Thread 变量 = new 自定义Thread();
调用线程对象的:start()来启动线程
注意:
1).重写的是run(),但启动线程调用的是start()
2).一个线程对象,只能start()一次,执行完毕,这个线程对象自动销毁,不能再次的start()
3).一个线程类,可以创建多个线程对象,每个线程对象都可以独立start(),以独立的线程的方式运行。
第二种方式 implements Runnable :
1.自定义Runnable类 implements Runnable接口
2.重写run()方法
3.启动线程:
创建一个自定义类对象
Thread t = new Thread(自定义Runnable类对象);
t.start();
======================================================
1).第一种需要子类 继承 Thread类,由于Java是单继承,所以对子类形成了限制。
2).第二种需要子类 实现 Runnable接口,对于子类就比较灵活,子类仍然可以继承其它类,
并且实现其它接口。
第一种实现线程的方式:自定义类 extends Thread
我们可以从Thread类中继承的方法:
1).run():必须重写,我们的线程中需要做的事情写到这里;
2).start():启动线程。不要重写(可以重写),重写后就无法启动线程了
3).getName():获取当前的线程名称。
每个线程都有一个默认的线程名称:Thread-索引
4).setName(String s):设置线程的名称。
--------------以下是一些静态方法--------------------------
5).public static Thread currentThread():获取当前的线程对象。
6).public static void sleep(long m):让当前线程休息m毫秒
但,如果多个线程访问同一个资源(变量、文件、数据库的数据…),就会产生多线程的并发问题,
使数据跟最终预想的不一样。
多线程安全问题前提:
1.多个线程
2.访问同一个数据
3.必须有修改/添加/删除元素的逻辑
1).对同一个变量访问的:可见性
多线程的问题一:变量的可见性
当两个线程共同访问一个变量时,如果一个线程将变量的值更改,
而另一个线程由于访问速度过快,而没有看到这个变量被修改,这就是:
由多线程运行机制导致的:变量的可见性问题。
2).对同一段代码访问的:有序性
程序在编译期间,虚拟机有可能把没有上下逻辑关系的代码进行代码顺序的打乱,叫重排.
一个线程的重排可能对其他线程造成影响,叫有序性问题.
编译器经常在不影响最终结果的前提下,为了提高编 译效率,会对一些代码进行"重排——不按照我们编写的顺序"
例如:
//我们编写的代码
int a = 10;
int b = 20;
int c = a + b;
//编译器为了提高编译效率,很可能在不影响最终结果的前提下,对上述代码进行"重排"
int b = 20;
int a = 10;
int c = a + b;
但是,在多线程情况下,这种"代码重排"可能就会使结果不确定!!!
所以,多线程情况下,我们不希望代码进行重排!!!
3).对同一个变量/一段代码访问的:原子性
1)保证内存的可见性,
2)禁止指令重排序,有序性
可以解决可见性的问题,只要给变量加上volatile,变量就会每次都重新获取
从JDK1.5开始,Java提供了一个新的包:java.util.concurrent(JUC包),这个包中提供了大量的"多线程"的工具类。
1).java.util.concurrent.atomic.AtomicInteger:可以保证多线程对一个int数值访问的原子性。
它内部定义了一个变量,来存储多线程操作的int值:
private volatile int value;
这样可以首先保证变量的:可见性、有序性;
然后通过getAndIncrement()和incrementAndGet()方法中的一些"机制"保证访问变量的"原子性"
什么机制:CAS机制(比较并交换)
2).java.util.concurrent.atomic.AtomicBoolean:对boolean变量访问的原子类
3).java.util.concurrent.atomic.AtomicLong:对long变量访问的原子类
4).java.util.concurrent.atomic.AtomicReference:对引用类型访问的原子类
1).java.util.concurrent.atomic.AtomicIntegerArray :可以对一个int[]数组做:原子操作。【主要演示】
2).java.util.concurrent.atomic.AtomicLongArray:对long[]数组操作的原子类
3).java.util.concurrent.atomic.AtomicReferenceArray:对引用类型数组操作的原子类
1).可见性
2).有序性
3).原子性
1).AtomicInteger:对int变量进行原子操作;
2).AtomicIntegerArray:对int[]数组进行原子操作;
如果需要将一段代码进行"原子操作"——一个线程执行这段代码时,其它线程不允许执行这段代码,要在外面排队
必须保证当前正在执行的线程将这段代码全部执行完毕,其它线程才可以执行这段代码。
Java提供一个重量级的对代码进行原子操作的关键字:synchronized(同步的)
public void show(){
synchronized(锁对象){
…
…
…
}
}
“锁对象”:它是一个"对象"——任何类的对象都可以。
重点:但必须保证"多个线程共同拥有同一个<锁对象>"
public synchronized void show(){
}
同步方法:
public synchronized void show(){//当一个线程访问此方法时,立即加锁,其它线程全部在外部等待。
当执行的线程将此方法全部执行完毕,会自动释放锁,其它线程才可以进入
//方法中所有的代码都是"同步代码"
}
同步方法的锁,是this~!!
因为只new了一个runnable,供三个线程使用。
所以三个线程共享同一个对象,该对象就是锁
静态同步方法的锁对象是类名.class
从JDK5开始,Java提供了另一种代替synchronized的锁:Lock锁
API文档中的示例代码:
Lock l = …;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
1).Lock锁中的代码如果出现异常,不会自动释放锁,程序就跟死了一样。
Lock锁的功能要比synchronized更强大,使用更灵活。在JUC包(java工具并发包)中的工具类,使用Lock锁的比较多
2).synchronized中的代码如果出现异常,会自动释放锁!在其它包中使用synchronized的比较多
面试题:
1).java.util.Vector:它是一个List集合,内部是用数组实现,从JDK1.0开始的
2).java.util.ArrayList:它也是一个List集合,内部也是用数组实现,从JDK1.2开始
3).java.util.concurrent.CopyOnWriteArrayList:它也是一个List集合,内部也是用数组实现,从JDK1.5开始
这三个类有什么区别:
1).最大的区别:
ArrayList是:线程不安全的;
Vector和CopyOnWriteArrayList:是线程安全的。
2).Vector和CopyOnWriteArrayList的区别:
1).Vector内部是用:synchronized实现的,synchronized被称为是:重量级锁(悲观锁)——效率比较低;无论是否有多线程竞争,都会拿锁,有额外的操作。
2).CopyOnWriteArrayList:内部是使用:CAS机制实现的:轻量级锁(乐观锁)——效率高。如果没有多线程竞争,不需要自旋,一次就可以修改成功。只有当有多线程竞争时,才有自旋的产生。
ArrayList 线程不安全的
Vector 老的,线程安全的 → 大部分方法均是Synchronized修饰的同步方法
CopyOnWriteArrayList 最新的,大部分方法,提供了线程安全的处理,如lock锁 底层使用CAS机制
C:compare 比较
A:and 和
S:swap 交换
CAS指通过逻辑,完成乐观锁的安全判断
悲观锁:无论别的线程有没有修改变量,我都认为修改了,要做安全处理
乐观锁:通过代码逻辑,在要判断同步安全问题的时候判断,不用判断的就不判断
具体的过程:
a线程 和 b线程 同时操作共享数据 (以a线程的视角)
a线程预先记录下共享数据的值
a线程调用方法访问集合元素时,会先获取当前的共享数据的值
情况1
预先值 == 当前值 (比较过程C)
没有其他线程操作过该元素,正常操作元素
情况2
预先值 != 当前值 (比较过程C)
终止当前操作,因为期间有其他线程修改了该共享数据
将预先值交换成当前值(交换过程S)
继续重头开始集合操作
一个线程对象不能反复的start(),只能start()一次
如果想再次使用此线程,需要再次创建此线程对象
作用:
1).可以缓存一些线程对象,让我们每次执行一个线程不需要创建新的对象,提高创建线程的效率。
2).可以限制多个线程的"并发数量"
Executors是一个"工具类",静态方法:newFixedThreadPool(int n):可以获取一个有固定容量的线程池对象n为线程个数
ExecutorService:线程池类
submit(Runnable run) 提交执行目标方法:向线程池提交要执行的内容。线程池分配线程执行。
提问:不传入Runnable,传入Thread可以么? 可以
shutdown() 关闭线程池
1).从JDK1.5开始的
2).之前我们实现线程的两种方式:继承Thread,实现Runnable接口,然后重写run()方法,这两种方式都有两个重要的缺点:
1).run()方法都不能返回值:
2).run()方法不能抛出"编译期异常"
3).实现Callable接口的好处,需要重写call(),此方法就可以:
1).返回值
Callable的call方法会有返回值Future对象,该对象包括了返回值结果,并且提供了一些有用的方法
Future的isDone方法,判断当前callable是否调用结束
Future的get方法,从对象中获取方法真正的返回值
2).可以抛出任何异常
4).实现方式:
第一步:自定义线程类,实现Callable接口,并重写call()方法
第二步:启动线程——必须使用"线程池"启动
当我们操作线程的某些方法时,会使线程处于某些状态下(见图1)
1).新建
start();
2).可运行:
3).计时等待(sleep)
4).锁阻塞(执行到被锁的方法)
5).无限等待(wait())
6).被终止(run()执行完毕)
等待(wait)和唤醒(notify): 它用于两个线程之间的配合
notify(), 没有线程咋等待的时候可以空唤醒
notifyAll() 唤醒所有线程
假如:
第一个线程先启动
工作...
发现一些问题...
wait()释放锁,进入到(无限等状态,等待被唤醒)
第二个线程拿到锁
工作...
工作完毕notify()唤醒在这个锁上等待的线程(不会释放锁)
释放锁
第一个线程拿到锁
恢复工作...
两个线程相互配合一个前提:必须共享同一把锁
Java类库中有一个线程的应用:定时器,可以让程序启动后,在指定的时间/日期时执行一次某个任务,也可以间隔指定的时间反复的执行某个任务
实现定时器:
1).java.util.TimerTask(抽象类):用于定义定时任务。我们需要继承此类,并重写它内部的run()方法,编写我们的任务。
2).java.util.Timer(类):用于启动定时任务的。
1).构造方法:
1).Timer():无参构造
2).成员方法:
1).void schedule(TimerTask task, long delay) 在指定的延迟之后安排指定的任务执行。【只执行一次】
2).void schedule(TimerTask task, long delay, long period):在delay延迟时间后执行task任务,每间隔period时间,重复执行此任务【往复执行】
3).void schedule(TimerTask task, Date time) 在指定的日期-时间安排指定的任务执行。【只执行一次】
4).void schedule(TimerTask task, Date firstTime, long period):在firstTime时间开始执行task任务,每间隔period时间,重复执行此任务【往复执行】
5).cancel,用于取消定时器
Lambda表达式:从JDK1.8开始的,它是一种"代替方案"——代替之前当我们调用一个方法,但方法的形参是一个接口,而且接口中只有一个抽象方法时(函数式接口),
此时,我们需要传入一个"子类",Lambda表达式就可以代替这个子类。
1).直接写方法体,编写简单
2).Lambda不是语法糖,编译后不是匿名内部类,整个过程没有定义类,没有创建对象,所以效率比较高。
1).可读性差。
2).不能重用。
3).破坏了Java面向对象的思想。
4).应用面很窄:必须面向"只有一个抽象方法的接口",必须是接口,而且必须有、且只有一个必须被子类重写的抽象方法(可以有其它的静态方法、默认方法…)。
不能是抽象类。
1).一对小括号():方法的形参部分
2).一个右箭头->:Lambda专属语法
3).一对大括号{}:重写的方法的方法体
Lambda表达式必须要面向"函数式接口":
对于这种"函数式接口"为了保证它的语法正确,通常会加注解:@FunctionalInterface——告诉编译器,下面的接口是一个函数式接口,如果不符合函数式接口的语法,则会编译错误
例如:类库中的Runnable接口:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
例如:自定义接口:
@FunctionalInterface
interface IA{
public abstract void eat();//子类必须重写
public abstract String toString();//写上这个抽象方法,就没有异常,仍然是一个合法的函数式接口。因为在Object类中有这个方法,任何类都会继承,所以子类可以不重写这个方法。
public boolean equals(Object o);
}
如果参数只有一个,可以同时省略:数据类型,一对小括号。
如果方法体中只有一句话,可以同时省略:语句后的分号、一对大括号、return关键字(如果有)
java.util.stream.Stream(接口):Stream流是JDK8新增的,它提供了很多方法结合一些函数式接口,可以对大量的数据进行多次的筛选,过滤、统计…操作。
我们可以将Stream流看做一个高级的迭代器,结合Lambda表达式
对大量数据进行遍历、筛选、过滤、统计等操作非常方便。
参数接收一个Consumer函数式接口,方法为accept(T t),代表对参数的处理
forEach内部会做一个循环,将每个元素作为"实参"调用一次accept(T t)方法
Consumer的使用可以简化为lambda方式使用
注意,每个stream只能使用一次,类比迭代器
参数接收一个Predicate函数式接口,方法为boolean test(T t),代表对参数的处理,返回true则符合条件,false则不符合条件
filter内部会做一个循环,将每个元素作为"实参"调用一次test(T t)方法
Predicate的使用可以简化为lambda方式使用
惰性求值——只有当我们执行"终结方法"时,之前的"拼接方法"才会被执行。
forEach()方法就是:终结方法,这种方法通常是最后执行。这种方法通常"没有返回值"。
filter()方法就是:拼接方法,这种方法通常在"终结方法"之前被调用。这种方法通常返回Stream流
注意:拼接方法后需要使用过滤后新返回的stream对象,再次使用。同一个stream只能使用一次
链式编程:对象在调用一个方法,返回对象后,再使用返回的对象调用方法,如此反复
用于将一种泛型的流(Stream)转换为另一种泛型(Stream)的流。该方法为拼接方法。 参数接收一个Function函数式接口,方法为R apply(T t),代表对T型参数的处理,返回R型数据 map(…)内部会做一个循环,将每个元素作为"实参"调用一次R apply(T t)方法
(7).Stream 的静态方法
static concat(Stream s1,Stream s2):将参数的两个流合并为一个流,返回。
collect(...):将流中的数据,转换为List集合、Set集合、数组。
参数接收一个Collector的toXXX方法,最终返回XXX容器