Java进阶
阅读源码
1.先了解这个类的所有实用的成员变量
2.了解这个类的构造方法
3.了解这个类的常用方法(方法的来源,分析父类接口中的定义方法)
泛型
广泛的类型(可以为任何类型),简化了代码的书写
<E>表示泛型,括号内可以为任意字母(通常为K、T、V、E),相当于一个占位符,在参数传入后才知道数据的类型

由于Java是一门静态语言,参数类型需要编译时确定
泛型的出现将类型的确定推迟到了代码运行时或者对象生成时
泛型出现就可以在方法中取规定存入的类型,再取出时方法就完成了类型的转换
泛型方法:在方法声明时定义的泛型是只在方法中使用的泛型,不是类上声明的泛型
// public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法 // 只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法 public <T> List<T> arrayToList(T<> arr)
泛型只能使用引用数据类型,不能使用基本数据类型
泛型只在编译期生效,在运行时泛型会被擦除,在编译时去规定类型,在运行时所有泛型都会变成Object类型,虽然泛型被擦除了,但是编译器会记住泛型(买票看演唱会你进场馆需要检票,进入场馆后就是一个正常人,但是系统有你的票据信息,查看你的信息的还能看到票)
泛型是一种标志,不会影响继承关系,两种泛型不同的对象,也是同一个类的对象
// 是同一个类的对象 Student<Object> s1 = new Student<>(); Student<String> s2 = new Student<>();
// 无界
SuperArray<?> superArray
// 上界
SuperArray<? extends Dog> superArray
// 下界
SuperArray<? super Dog> superArray
集合类
在Java中实现了collection接口的类是集合,集合是一种容器,也就是一种数据结构,用来存储数据
框架中,接口和实现类之间通常存在一个抽象类

集合类常用的实现类:HashMap和ArrayList
集合和数组的区别:
1.数组长度不可变,集合类长度可变
2.数组提供的方法有限,对于添加、删除、插入数据操作非常不方便,并且效率不高
3.数组中存储数据的特点是:有序、可重复的,对于无序、不可重复的需求,不能满足。
4.数组中可以存储基本数据类型,也可以存储引用类型。在集合中只能保存引用类型(保存的是对象的引用地址)
Collection接口
Collection集合是Java提供的一个框架,里面写了很多的类用于使用
Collection就是这个框架的一个顶层接口
Collection定义了一些操作“集合”的方法,是整个集合框架的一个顶层接口,所有的实现类都应该具有这些功能
Collection没有提供get、set方法,有些集合没有这些方法,所以就需要一些子类去定义
List接口
List接口中定义了一些操作集合的一些方法,比如根据索引获取,根据数据修改的方法
List接口的实现类都是默认为有索引的连续结构 数据结构、线性表、链表
List接口中声明的都是一些根据索引(下标)去进行操作的方法,所以List接口的实现类都是可以通过索引的形式去操作的
规定存储的内容,可以存储重复的数据,并且存储结构有序,可以拿到集合中的数据
常用实现类:
ArrayList:底层是基于数组,属于线性表,查询快,增删慢,线程不安全,初始化容量为0,扩容时扩容1.5倍
Vector:和ArrayList相似,线程安全,但一般不使用Vector来实现线程安全,使用CopyOnWriteArrayList来实现,初始化容量为10,扩容时扩容2倍
LinkedList:底层是链表,查询慢,增删快
ArrayList

ArrayList源码
成员变量:int DEFAULT_CAPACITY=10,默认初始化容量
Object[] EMPTY_ELEMENTDATA={}默认的空存储数组
ArrayList的底层存储结构,ArrayList的存储结构是数组
默认是空数组,数组如果扩容就需要占用内容,所以在没有任何元素时,空数组开销最小
Object[] elementData:存储数据的数组,没有进行赋值,在使用时根据容量来进行开辟空间
int size ArrayList的长度
构造器:ArrayList():将空数组赋值给elementData,在没有使用时,赋值的空数组,使用时会重新声明一个长度为10的默认数组
ArrayList(int initialCapacity):存入初始化容量进行数组的初始化
public ArrayList(Collection <? extends E> c):根据所传入的集合进行初始化
<?extends E>:泛型,规定了这个集合指定存储指定的元素
?extends E:存储的元素只能是E或者E的子类
E就是在创建ArrayList所指定的泛型类型,默认为Object类型
elementData = Arrays.copyOf(a,size,Object[].class):将a的所有元素都转化成Object类型的数据,并存储到一个长度为size的数组中
ArrayList是有容量的,如果新增时超过了最大容量,会自动扩容
自动扩容机制
第一步:将原容量扩充1.5倍
第二步:判断扩充过后的容量是否足够,如果不够就让最小容量为最新容量
第三步:判断新容量是否过大,如果过大,最新容量为Inteter.MAX-8
ArrayList相关方法
public class ArrayListDemo {
public static void main(String[] args) {
ArrayList<Object> arrayList= new ArrayList<>(10);
// add 在ArrayList尾部增加数据
arrayList.add(11);
arrayList.add("111");
arrayList.add(true);
System.out.println(arrayList);
// get 获取指定下标处的数据
System.out.println(arrayList.get(1));
// set 将指定下标处的值替换
arrayList.set(0,1111);
System.out.println(arrayList);
// remove 删除指定下标处的值 把指定位放到最后并设为null,没有改变容量
arrayList.remove(2);
System.out.println(arrayList);
// toArray 转换成一个数组
Object[] objects = arrayList.toArray();
// addAll 将另一个ArrayList的所有值增加到指定ArrayList中
ArrayList<Object> arrayList1 = new ArrayList<>();
arrayList1.add(222);
arrayList1.add("2222");
arrayList.addAll(arrayList1);
System.out.println(arrayList);
// subList 输出指定下标之间的数据(前算后不算)
System.out.println(arrayList.subList(0, 3));
// removeAll 删除指定的ArrayList
arrayList.removeAll(arrayList1);
System.out.println(arrayList);
}
}
ArrayList常用方法
sort方法
Comparable:比较规则,通常是去规定一个类的自我比较规则
public int compareTo(T o); 自己和o进行比较,返回值为负数,0,整数,分别代表小于,等于,大于
// 定义学生类
//Comparable需要在创建类时就定义比较规则
public class Student implements Comparable<Student>{//继承Comparable接口
private Integer id;
private Integer score;
public Student() {
}
public Student(int id, int score) {
this.id = id;
this.score = score;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return id == student.id && score == student.score;
}
@Override
public int hashCode() {
return Objects.hash(id, score);
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", score=" + score +
'}';
}
// 实现comparaTo方法
@Override
public int compareTo(Student o) {
return this.getScore() - o.getScore();//从小到大
//return o.getScore() - this.getScore();//从大到小
}
}
// Comparable应用
public static void main(String[] args) {
ArrayList<Student> arrayList = new ArrayList<>();
arrayList.add(new Student(1, new Random().nextInt(100)));
arrayList.add(new Student(1, new Random().nextInt(100)));
arrayList.add(new Student(2, new Random().nextInt(100)));
arrayList.add(new Student(2, new Random().nextInt(100)));
Collections.sort(arrayList);
System.out.println(arrayList);
}
Comparator:排序规则,每一个实现类就是一种规则
int compare(T o1, T o2); 比较器的一个比较规则
// 定义学生类
public class Student{
private Integer id;
private Integer score;
public Student() {
}
public Student(int id, int score) {
this.id = id;
this.score = score;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return id == student.id && score == student.score;
}
@Override
public int hashCode() {
return Objects.hash(id, score);
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", score=" + score +
'}';
}
}
// Comparator应用
public static void main(String[] args) {
ArrayList<Student> arrayList = new ArrayList<>();
arrayList.add(new Student(1, new Random().nextInt(100)));
arrayList.add(new Student(1, new Random().nextInt(100)));
arrayList.add(new Student(2, new Random().nextInt(100)));
arrayList.add(new Student(2, new Random().nextInt(100)));
Comparator<? super Student> comparator1 = new Comparator<Student>() {
@Override
// 从大到小
public int compare(Student o1, Student o2) {
return o1.getScore() > o2.getScore() ? -1 : o1.getScore() == o2.getScore() ? 0 : 1;
//等价于
//return o2.getScore() - o1.getScore();
//return Integer.compare(o2.getScore(), o1.getScore());
}
};
Comparator<? super Student> comparator2 = new Comparator<Student>() {
@Override
// 从小到大
public int compare(Student o1, Student o2) {
return o1.getId() > o2.getId() ? 1 : o1.getId() == o2.getId() ? 0 : -1;
//等价于
//return o1.getId() - o2.getId();
//return Integer.compare(o1.getId(), o2.getId());
}
};
arrayList.sort(comparator1);
arrayList.sort(comparator2);
System.out.println(arrayList);
}
LinkedList
LinkedList是List的实现类,是AbstarctList抽象类的子类
ArrayList,LinkedList的父类结构是差不多的
ArrayList的底层是线性表结构(一个数组,ArrayList需要扩容的原因),LinkedList的底层是链表结构(一个一个的节点)
区别:
线性表是通过索引下标查询,查找和修改速度快,而插入和删除时需要修改部分数据的下标
链表是通过节点中的头指针或者尾指针去查询下一个元素,插入和删除速度快
LinkedList的难点在于怎么去设计节点
LinkedList源码:
成员变量:Node<E> first:链表的头节点
Node<E> last:链表的尾节点
内部类:Node<E> 链表的节点,里面三个属性分别是前一个节点,后一个节点,和当前节点存储的值
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
尾插:把元素插入到集合的最后
LinkedList增删改查
增
add();
// 源码
public boolean add(E e) {
linkLast(e);
return true;
}
// 判断当前节点是否是头节点也是尾节点(即插入前链表是否为空)
void linkLast(E e) {// 参数是要插入的元素
final Node<E> l = last;// 保存插入前的尾节点
final Node<E> newNode = new Node<>(l, e, null);// 构建存储e的节点,插入到最后,这个节点前就是之前的尾结点,这个节点就是现在的尾结点,next应该为null
last = newNode;// 将新的节点赋值为尾结点
if (l == null)// 判断插入前尾节点是否为空,为空表示链表为空,需要将新节点也赋值为头节点
first = newNode;
else// 如果不为空,就把之前尾节点的next指向当前节点
l.next = newNode;
size++;// 集合长度
modCount++;
}
删
remove();
// 源码
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
//判断删除的元素是否存在于链表中
if (x.item == null) {// 不存在
unlink(x);
return true;
}
}
} else {// 存在
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
// 删除操作做的其实就是断开前驱节点与后继节点的关系,不为空
E unlink(Node<E> x) {// 当一个节点不被任何一个节点指向时,该节点就不属于这个链表
// assert x != null;
final E element = x.item;// 保存当前节点存储的值 作为返回值
final Node<E> next = x.next;// 当前节点的前节点
final Node<E> prev = x.prev;// 当前节点的后节点
// 判断前驱节点是否为空,即删除的节点是否为头节点
if (prev == null) {// 为空直接将头节点赋值为被删除元素的尾节点
first = next;
} else {// 不为空,直接将前驱节点的尾节点赋值为next,即被删除元素的尾节点
prev.next = next;
x.prev = null;// 并且断开x与前驱节点的关系
}
// 判断后继节点是否为空,即被删除的节点是否是尾节点
if (next == null) {// 如果是尾节点,那么设置尾节点为被删除节点的前驱节点
last = prev;
} else {// 如果不是尾节点,那么直接将尾结点的前驱节点赋值为被删除节点的前驱节点
next.prev = prev;
x.next = null;
}
x.item = null;// 删除当前节点的元素
size--;// 长度减一
modCount++;
return element;
}
改
set();
// 源码
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
// 判断指定索引处是否在容量内,不在则报异常
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 判断指定索引处是否在容量内
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
// 找到对应index的节点
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {// 由于LinkedList是双向链表,判断当前要查的节点是在前半段还是在后半段
Node<E> x = first;// 从头节点开始遍历每个节点的next
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;// 从尾节点开始遍历每个节点的prev
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
查
get();
// 源码
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
// 判断指定索引处是否在容量内,不在则报异常
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 判断指定索引处是否在容量内
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
// 找到对应index的节点
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {// 由于LinkedList是双向链表,判断当前要查的节点是在前半段还是在后半段
Node<E> x = first;// 从头节点开始遍历每个节点的next
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;// 从尾节点开始遍历每个节点的prev
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
Set接口
Collection的一个子接口,和List接口并列
HashSet TreeSet LinkedHashSet
Set集合中存储的是无序的不重复的数据,存储结构无序,无法获取单个元素
HashSet的底层是Hash表结构,存储的元素无序且不重复
当添加了重复的值时,HashSet会自动将其去除
Set或者说Hash表是如何保证元素不重复的?
答:需要重写hashcode和equals方法
Set和List的相互转换
//将list转换成set
ArrayList<String> list = new ArrayList<>();
list.add("富强");
list.add("民主");
list.add("文明");
// 传入list参数
//同样的方法可以将set转换成list
HashSet<String> set = new HashSet<>(list);// 可去重,去重最简单的方法
System.out.println(set);
常用实现类:
HashSet:存储的数据无序,且唯一,底层是一个HashMap
LinkedHashSet:HashSet的子类,存储的数据有序,且唯一,底层是一个LinkedHashMap,每一个节点都有指针去指向下一个节点
TreeSet:把元素进行了排序,TreeSet中存储的元素类必须实现Comparable接口
Map接口
是集合框架的顶层接口,和Collection并列
Map集合是用来存储键值对结构的 key:value结构 即函数映射结构,每个key有且仅有一个对应的value,value可以被多个key映射,所有在Map接口中有一个子接口Entry<K,V>,在Map中存储的就是Entry,每一个Entry都对应了一个键值对
由于一个键只能指向一个值,所以Map中的key不能重复
Map的常用实现类:
HashMap:底层是哈希表+链表+红黑树结构(长度大于8时),存储的是键值对,HashMap的key值可以为null
// HashMap的底层是哈希表+链表+红黑树结构
Map<String,String> map = new HashMap<>();
// 增
map.put("001","张三");
map.put("002","李四");
map.put("003","王五");
System.out.println(map);
// 删
map.remove("001");
System.out.println(map);
// 改
map.put("002","张三");
System.out.println(map);
// 查
System.out.println(map.get("002"));
// keyset 输出所有的键
System.out.println(map.keySet());
// entryset 输出所有的键值对
System.out.println(map.entrySet());
LinkedHashMap:在HashMap的基础上给每个节点都添加了前后指针,保证了顺序
HashTable:和HashMap一样,但线程安全,在线程安全问题时,使用ConcurrentHashMap。HashMap是效率最高的,HashTable是效率最低的,HashTable的key值不可以为null。Properties继承于HashTable
TreeMap:把节点用Key进行了排序
当添加键值对时,如果键名重复,就会将之前的键所对应的值所覆盖
HashMap:
内部类:
// Node内部类
// Node<K,V>节点就是用来存储HashMap数据的节点,每一个键值对都对应着一个Node对象
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;// 节点对应的hash值
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
属性:
// 哈希表,用来存储Node节点,是根据Node的HashCode决定存储在哪个索引位置 transient Node<K,V>[] table; // HashMap存储键值对的结构就是Entry的实现,就是存储所有的Entry transient Set<Map.Entry<K,V>> entrySet; // 负载因子,默认为0.75 final float loadFactor; // 默认容量为16,如果负载因子为0.75,当table存储了12个伪元素后就会进行扩容 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 等于容量*负载因子 int threshold; // 当链表长度超过8时就会树化 static final int TREEIFY_THRESHOLD = 8; // 当树的节点数小于6时就会链表化 static final int UNTREEIFY_THRESHOLD = 6;
循环遍历
List
for,fori,foreach
public static void main(String[] args) {
ArrayList<Person> arrayList = new ArrayList<>();
Random random = new Random();
// 添加元素
for (int i = 0; i < 5; i++) {
arrayList.add(new Person(random.nextInt(100)));// 生成一个随机的int值,该值介于[0,100)的区间
}
// for
for (Person person : arrayList) {
System.out.print(person);
}
System.out.println();
// fori
for (int i = 0; i < arrayList.size(); i++) {
System.out.print(arrayList.get(i));
}
// foreach(通过匿名内部类实现Consumer接口)
arrayList.forEach(new Consumer<Person>(){
@Override
public void accept(Person p) {
System.out.println(p);
}
});
// foreach(通过lambda表达式实现Consumer接口)
list.forEach(one->{
System.out.println(one);
});
}
Set
for
public static void main(String[] args) {
Set<Person> set = new HashSet<>();
Random random = new Random();
for (int i = 0; i < 5; i++) {
set.add(new Person(random.nextInt(100)));
}
// for
for (Person person : set) {
System.out.println(person);
}
}
Map
entrySet
public static void main(String[] args) {
HashMap<String, Person> hashMap = new HashMap<>();
Random random = new Random();
for (int i = 0; i < 5; i++) {
hashMap.put(i+"",new Person(random.nextInt(100)));
}
System.out.println(hashMap);
// 获取所有键
System.out.println(hashMap.keySet());
// 获取所有值
System.out.println(hashMap.values());
// 获取所有键值对
Set<Map.Entry<String,Person>> entries = hashMap.entrySet();
System.out.println(entries);
// entrySet遍历Map
for (Map.Entry<String,Person> entry: hashMap.entrySet()) {
System.out.println(entry);
}
}
迭代器
ArrayList和LinkedList所独有的
public static void main(String[] args) {
ArrayList<Person> arrayList = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < 5; i++) {
arrayList.add(new Person(random.nextInt(100)));
}
// 创建迭代器
Iterator<Person> iterator = arrayList.iterator();
// 迭代器遍历
while (iterator.hasNext()) {
Person next = iterator.next();
System.out.println(next);
}
}
集合工具类Collections
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1);
arrayList.add(3);
arrayList.add(2);
ArrayList<Integer> arrayListA = new ArrayList<>();
arrayListA.add(6);
arrayListA.add(8);
arrayListA.add(7);
System.out.println(arrayList);
System.out.println(arrayListA);
// sort方法 对元素进行排序
Collections.sort(arrayList);
System.out.println("sort:"+arrayList);
// shuffle方法 打乱当前顺序
Collections.shuffle(arrayList);
System.out.println("shuffle:"+arrayList);
// copy方法 用后者替换前者
Collections.copy(arrayListA,arrayList);
System.out.println("copy:"+arrayListA);
// addAll方法 添加元素到原集合中
Collections.addAll(arrayList,1,2,3,4,5);
System.out.println("addAll:"+arrayList);
// reverse方法 反转
Collections.reverse(arrayList);
System.out.println("reverse:"+arrayList);
}
枚举类
使用enum来声明类
枚举类就是一个对象数量有限的类,并且对象都在类中定义好
public enum Sesson {
SPRING("春天"),SUMMER("夏天"),AUTUMN("秋天"),WINTER("冬天");
String name;
Sesson(String name) {
this.name = name;
}
}
枚举类无法继承其他类,只能继承Enum类型的类
例:
public class ResultInfo<T> {
Integer code;
String message;
T data;
ResultInfo() {
}
ResultInfo(Integer code, String message) {
this.code = code;
this.message = message;
}
public ResultInfo(Integer code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public Integer getState() {
return code;
}
public void setState(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
// 定义一个枚举类,其中状态码只有成功和失败
enum StateCode {
FAILED(500),SUCCESS(200);
Integer code;
StateCode(Integer code) {
this.code = code;
}
}
@Override
public String toString() {
return "ResultInfo{" +
"code='" + code + '\'' +
", message='" + message + '\'' +
", data=" + data +
'}';
}
}
// 测试
public static void main(String[] args) {
System.out.println(new ResultInfo<String>(ResultInfo.StateCode.SUCCESS.code, "成功", "数据"));
}
常用方法

异常
异常出现时,程序就结束,异常后的语句就不会执行
令异常不发生:修改代码,检测参数
使异常发生后继续执行:发生异常后捕获并处理异常
Throwable类是java中所有异常和错误的超类
Error(错误):严重问题,会导致JVM虚拟机崩溃或是代码无法运行,一般无法捕获处理,需要修改代码或修改环境
exception(异常):在代码运行的时候发生的不正常情况
1.受检异常:这种异常必须在编译时就进行捕获
2.非受检异常:这种异常通常可以通过修改代码或逻辑去避免的异常
3.自定义异常:写一个检查性异常类,则需要继承 Exception 类;写一个运行时异常类,那 么需要继承 RuntimeException 类
//自定义一个异常类
public class SjException extends Exception{
//无参构造
public SjException() {
}
//有参构造,并且使用super调用父类的构造方法,传递异常信息
public SjException(String message) {
super(message);
}
}
//自定义异常类:三角形的判断和面积
//自定义类Sanj,其中有成员 x,y,z,作为三边长,构造方法Sanj(x,y,z)分别 给x,y,z赋值
//方法求面积getArea和显示三角形信息(三个边长)showInfo
//这2个方法中当三条边不能构成一个三角形时要抛出自定义异常SjException,否则显示正确信息
//在另外一个类中的主方法中构造一个Sanj对象(三边为命令行输入的三个整数),显示三角形信息和面积,要求捕获异常
// Sanj类
public class Sanj {
private int x;
private int y;
private int z;
public Sanj() {
}
public Sanj(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
public void getArea() throws SjException {
if (x + y > z && x + z > y && y + z > x) {
double p = (double) (x + y + z) / 2;
double a = Math.pow(p * (p - x) * (p - y) * (p - z), 1 / 2);
System.out.println("三角形的面积为:"+a);
} else {
throw new SjException("不能构成三角形");
}
}
public void showInfo() throws SjException {
if (x + y > z && x + z > y && y + z > x) {
System.out.println("三角形的三边分别为:"+x+","+y+","+z);
} else {
throw new SjException("不能构成三角形");
}
}
}
// 测试类
public class TriangleAreaTest {
public static void main(String[] args) {
Sanj sanj = new Sanj(2,2,3);
try {
sanj.getArea();
sanj.showInfo();
} catch (SjException e) {
e.printStackTrace();
}
}
}
try...catch
catch 语句包含要捕获异常类型的声明。当保护代码块中发生一个异常时,try 后面的 catch 块就会
被检查。如果发生的异常包含在 catch 块中,异常会被传递到该 catch 块,这和传递一个参数到方
法是一样。
在使用try...catch时,若第一个catch中写入的是(Exception e),则该catch后的再写入其他的catch则会发生编译错误
try {
// 程序代码
} catch(ExceptionName e1) {
//Catch 块
}
finall
finally 关键字用来创建在 try 代码块后面执行的代码块。无论是否发生异常,finally代码块中的代码总会被执行。
try{
// 程序代码
}catch(异常类型1 异常的变量名1){
// 程序代码
}catch(异常类型2 异常的变量名2){
// 程序代码
}finally{
// 程序代码
}
try...catch...finally
public static int test(){
//try语句块中有 return 语句时,会先执行try,然后将值保存在i中用于return,然后再执行finally
int i = 1;
try{
i++;
System.out.println("try block, i = "+i);
return i;
}catch(Exception e){
i ++;
System.out.println("catch block i = "+i);
return i;
}finally{
i = 10;
System.out.println("finally block i = "+i);
}
}
// 输出
try block, i = 2
finally block i = 10
2
public static int test(){
//finally语句块中有 return 语句时,最终会采用 finally 代码块中的 return 语句进行返回,而直接忽略 try 语句块中的
return 指令
int i = 1;
try{
i++;
System.out.println("try block, i = "+i);
return i;
}catch(Exception e){
i++;
System.out.println("catch block i = "+i);
return i;
}finally{
i++;
System.out.println("finally block i = "+i);
return i;
}
}
// 输出
try block, i = 2
finally block i = 3
3
线程
并发:两个或多个事件在同一时间段内发生,一个cpu去执行多件事情
并行:两个或多个事件在同一时刻发生, 多个cpu去执行多件事情
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运
行多个线程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个
进程从创建、运行到消亡的过程。
线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个
进程 中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程。
程序是在所有线程结束后才结束
多线程:在一个进程中,用多个线程去执行任务,只占用一个cpu
JVM只是一个进程,只会占用一个cpu
当只有一个任务时,不管是单线程还是多线程,都只是使用了一个cpu,也就是总体效率不变,多线程没有提高速度,但由于多线程之间还要抢占cpu执行权,并且要进行通信,所以多线程比单线程慢
java程序启动至少启动两个线程
1.main线程
2.gc线程 垃圾回收
多线程在宏观角度就是多个事情在同时发生
多线程在微观角度就是main线程和多线程的其他线程在抢cpu的执行权,每一个执行权就是一个时间片
创建线程:
一.继承Thread类
1.继承Thread类
2.重写run方法,run方法中的内容,就是这个线程要去执行的任务
3.创建子类对象调用start方法,启动run任务
二.实现Runnable接口
1.实现Runnable接口
2.实现run方法,run方法中的内容就是这个线程要去执行的人物
3.创建子类对象,通过子类对象创建Thread类对象
4.调用Thread类对象的start()方法
Runnable方式创建相对于Thread方式创建的好处:
适合多个相同的程序代码的线程去共享同一个资源,多个线程去操作一个对象。 可以避免java中的单继承的局限性。
增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
线程池只能放入实现Runnable或Callable类线程,不能直接放入继承Thread的类。
三.匿名内部类创建
// 新增线程记录时间
// 超时挑战失败
new Thread(new Runnable() {// 匿名内部类
@Override
public void run() {
try {
Thread.sleep(10*1000);// 睡眠10秒,10秒后触发
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("挑战失败");
System.exit(0);
}
}).start();
//lambda表达式实现匿名内部类以创建线程的方式
new Thread(()->{
System.out.println("线程开启");
}).start;
四.实现Callable接口(这个线程是有返回值的)
1.实现Callable接口
2.创建实现类对象,通过实现类对象创建FutureTask对象
3.通过FutureTask对象生成Thread对象
4.调用Thread对象的start()方法启动线程
5.调用FutureTask对象的get()方法获取返回值
get()方法是一个阻塞方法
阻塞方法:会阻塞线程,直到获取到值为止(让自身线程执行完毕)
五.线程池创建
使用Executors线程池工具类,通过提供的静态方法创建线程池
线程池是用来存放线程的,让线程可以重复使用
1.创建线程池
2.创建线程
3.把线程放入线程池中,并执行
public class ThreadPool {
public static void main(String[] args) {
// 创建线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
// 创建线程
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
}
};
// 将线程放入线程池
// 开启三个线程
pool.submit(runnable);
pool.submit(runnable);
pool.submit(runnable);
}
}
线程的启动就是调用start方法,start方法会调用当前Thread对象的run()方法
使用Runnable接口的形式创建的线程会传入target属性,这个就是被调用run方法的线程对象
Thread的run方法,会判断是否执行target的run方法
Thread常用方法
静态方法
1.currentThread():获取当前正在运行的线程
2.yield():让步,让出当前的执行权。让步后,当前线程依然可以抢执行权
if (i%3 == 0) { // 当i为3或3的倍数时,让权
thread.yield();
}
3.sleep(long mills):让当前进程进入睡眠,睡眠时间为mills毫秒,睡眠时是进入了阻塞状态,时间结束后进入就绪状态,等待cpu的调用
if (i%3 == 0) { // 当i为3或3的倍数时,等待1秒
thread.sleep(1000);
}
普通方法
1.start():启动当前线程
2.getName():获取当前线程的名字
3.setName():设置当前线程的名字
4.interrupt():发起中断请求,请求中断当前进程,只要当前进程进入阻塞状态就会中断
5.isAlive():检测当前线程是否存活(只有当一个线程全部执行完后才会死亡),返回的是一个boolean值
// 求1到100000之间的质数,判断多线程的时长
long begin = System。currentTimeMillis();
long end = 0;
Thread thread1 = new Thread(new MyThread(0,20000));
Thread thread2 = new Thread(new MyThread(20000,40000));
Thread thread3 = new Thread(new MyThread(40000,60000));
Thread thread4 = new Thread(new MyThread(60000,80000));
Thread thread5 = new Thread(new MyThread(80000,100000));
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
while (true) {
if (!thread1.isAlive() && !thread2.isAlive() && !thread3.isAlive() && !thread4.isAlive() && !thread5.isAlive()) {
end = System.currentTimeMillis();
System.out.println(end-begin);
break;
}
}
// MyThread类
Integer begin;
Integer end;
(构造方法)
public void run() {
for (int i = begin; i < end; i++) {
boolean flag = true;
for (int j = 2; j < i; j++) {
if (i%j == 0) {
flag = false;
}
}
if (flag) {
System.out.println(i);
}
}
}
6.setProority():设置优先级,10最高,1最低,默认为5(值越高,抢占概率越大)
7.join():等待当前线程死亡,即等待当前线程执行完,会使调用这个方法的线程进入阻塞状态
mt.join(); // “当前线程”指的是该方法调用写在哪个线程中,若写在main中,则是等待main线程执行完后执行mt线程
8.setDaemon():设置当前线程是否为守护线程
用户线程:独立线程,主线程结束之后,当前线程依然会继续执行直到结束
守护线程:如果当前线程是某一个线程的守护线程,会随着那个线程的结束而结束
9.getState():获取当前线程的状态
线程安全问题:数据共享时不同步。
解决:
1.使用同步代码块synchronized (o),代码块中的内容只能有一个线程进入,获取到o这个同步锁(可以使用任意一个对象来作为同步锁)的线程进入,没有获取到锁的线程会进入阻塞状态,直到获取锁后再运行。sleep方法会进入阻塞状态,但不会释放锁;wait方法进入阻塞状态后,会释放锁
(有同步锁时才能使用下列方法)
wait方法:
使当前线程进入阻塞态,并释放锁
wait方法是Object提供的方法
wait方法可以不填时间,表示无限等待
notify方法:
notify方法是Object提供的方法
随机唤醒一个wait的当前对象
notifyAll方法:
唤醒所有的wait对象
public class PrintTest {
public static void main(String[] args) {
Object lock = new Object();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
for (int i = 0; i < 27; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
try {
lock.notify();// 不要先睡后唤醒,否则会“睡死”
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.exit(0);
}
}
}, "数字线程").start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
for (int i = 0; i < 26; i++) {
char c = (char) ((char) i + 'A');
System.out.println(Thread.currentThread().getName()+":"+c);
try {
lock.notify();// 唤醒打印数字的线程
lock.wait();// 等待数字被打印完
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.exit(0);
}
}
},"字母线程").start();
}
}
2.使用同步方法public synchronized void 方法名(){ }
public class Ticket implements Runnable{
private int ticket = 100;
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while(true){
sellTicket();
}
}
/*
* 锁对象 是 谁调用这个方法 就是谁
* 隐含 锁对象 就是 this
*
*/
public synchronized void sellTicket(){
if(ticket>0){//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取当前线程对象的名字
System.out.println(Thread.currentThread().getName()+"正在
卖:"+ticket‐‐);
}
}
}
3.使用lock锁,首先在成员位置创建一个ReentrantLock对象,然后,在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁最后,在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
public class Ticket implements Runnable{
private int ticket = 100;
Lock lock = new ReentrantLock();
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while(true){
// 上锁
lock.lock();
if(ticket>0){//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在卖:"+ticket‐‐);
}
// 解锁
lock.unlock();
}
}
}
死锁
必要条件:
1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释
放。
3、请求和保持,即当资源请求者在请求其他资源的同时保持对原有资源的占有。
4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这
样就形成了一个等待环路。
(重要)线程状态:
1.出生:创建,new,线程对象被创建时处于这个状态
2.就绪:线程对象调用start方法时的状态,可以被执行态
3.运行:cpu调度当前进程
4.阻塞:因为获取不到锁,或者调用sleep,wait,join等方法进入等待状态
5.销毁:线程执行完成任务,自我毁灭
IO流
输入和输出
输入:从硬盘中把数据读取到程序中
输出:从程序中把数据写入到硬盘中
文件和文件夹用于存储各种各样的数据,在Java中,文件也是一个对象,而File类中就有操作文件的方法
在Windows系统中,每一层级用反斜杠 \ 表示(在Java中,由于反斜杠是有特殊含义的,所以需要使用反斜杠将反斜杠转译成无意义的字符,即用两个反斜杠);Linux系统中,用斜杠 / 表示
如果指定路径没有对应文件,同样也会生成一个File对象
绝对路径:这个文件所在位置的整体盘符路径
相对路径:这个文件从当前文件开始的路径
File类的构造器:
File(String path):使用绝对路径或者相对路径找到文件
File(String parent,String child):父路径和子路径创建
File(File parent,String child):父类文件夹和子路径创建
File file = new File("D:\\img\\123.txt");
System.out.println(file.length());
File file1 = new File("second_stage\\io\\src\\com\\moon\\123.txt");// 左上角路径
System.out.println(file1.length());
File file2 = new File("D:\\img");
System.out.println(file2.isDirectory());
File file3 = new File(file2, "123.txt");
System.out.println(file3.getName());
File file4 = new File("123.txt", "123.txt");
System.out.println(file4.getName());
File常用方法:
获取功能的方法:
public String getAbsolutePath() :返回此File的绝对路径名字符串。
public String getPath() :将此File转换为路径名字符串。
public String getName() :返回由此File表示的文件或目录的名称。
public String getParent():获取上层文件目录路径。若无,返回null
public long length() :返回由此File表示的文件的长度。
public long lastModified():获取最后一次的修改时间,毫秒值
public String[] list():获取指定目录下的所有文件夹或者文件目录的名称数组
public File[] ListFiles():获取指定目录下的所有文件或者文件目录的File数组
判断功能的方法
public boolean exists():此File表示的文件或目录是否实际存在
public boolean isDirectory():此File表示的是否为目录
public boolean isFile():此File表示的是否为文件
创建删除功能的方法
public boolean createNewFile() :当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。
public boolean delete() :删除由此File表示的文件或目录。
public boolean mkdir() :创建由此File表示的目录。
public boolean mkdirs() :创建由此File表示的目录,包括任何必需但不存在的父目录。
File f = new File("aaa.txt");
System.out.println("是否存在:" + f.exists());
System.out.println("是否创建:" + f.createNewFile());
System.out.println("是否存在:" + f.exists());
// 目录的创建
File f2 = new File("d:\\aaa");
System.out.println("是否存在:" + f2.exists());
System.out.println("是否创建:" + f2.mkdir());
System.out.println("是否存在:" + f2.exists());
// 创建多级目录
File f3 = new File("d:\\workspace");
System.out.println(f3.mkdir());
File f4 = new File("d:\\workspace");
System.out.println(f4.mkdirs());
// 文件的删除
System.out.println(f.delete());
// 目录的删除
System.out.println(f2.delete());
System.out.println(f4.delete());
// 输出
是否存在:false
是否创建:true
是否存在:true
是否存在:false
是否创建:true
是否存在:true
true
false
true
true
true
目录的遍历
public String[] list() :返回一个String数组,表示该File目录中的所有子文件或目录。
public File[] listFiles() :返回一个File数组,表示该File目录中的所有的子文件或目录。
File file6 = new File("D:\\img");
String[] name = file6.list();
for (String s : name) {
System.out.println(s);
}
File[] files = file6.listFiles();
for (File file7 : files) {
System.out.println(file7);
}
// 输出
123.txt
456.txt
789.txt
D:\img\123.txt
D:\img\456.txt
D:\img\789.txt
递归思想
//递归调用方法
//自己调用自己的逻辑去处理斐波那契数列
//1 1 2 3 5 813213455定义方法获取当前位置的数是多少
//第10位我就需要直到第8位和第9位,我可以再次去调用我自己这个逻辑,直到去找到第一位和第二位的值,由于直到第一位和第二位的值为1 所以递归一定要具有一个终点不然程序就无法结束会导致栈溢出
public static void main(String[] args) {
System.out.println(getI(10));
}
public static int getI(int i) {
if (i == 1 || i == 2) {
return 1;
}
return getI(i-1)+getI(i-2);
}
递归遍历文件
public class DiGuiDemo2 {
public static void main(String[] args) {
// 创建File对象
File dir = new File("D:\\workspace");
// 调用打印目录方法
printDir(dir);
}
public static void printDir(File dir) {
// 获取子文件和目录
File[] files = dir.listFiles();
// 循环打印
for (File file : files) {
//对遍历得到的File对象f进行判断,判断是否是文件夹
if (file.isFile()) {
// file是文件,输出文件绝对路径
System.out.println("文件名:"+ file.getAbsolutePath());
} else {
// 是目录,输出目录绝对路径
System.out.println("目录:"+file.getAbsolutePath());
// 继续遍历,调用printDir,形成递归
printDir(file);
}
}
}
}
文件过滤器 6/20/20:34
FileFilter接口
实现accept方法,接收参数为file的对象,如果保留这个对象,则返回true,过滤掉的就返回false
// 代码
//过滤器FileFilter的实现类:
public class FileFilterImpl implements FileFilter{
@Override
public boolean accept(File pathname) {
//如果pathname是一个文件夹,返回true,继续遍历这个文件夹
if(pathname.isDirectory()){
return true;
}
return pathname.getName().toLowerCase().endsWith(".java");
}
}
// 测试类1
// 在遍历的基础上添加一个过滤器,本例的过滤器作用是过滤掉不是以java结尾的文件
public class Demo01Filter {
public static void main(String[] args) {
File file = new File("D:\\workspace");
getAllFile(file);
}
public static void getAllFile(File dir){
File[] files = dir.listFiles(new FileFilterImpl());//传递过滤器对象
for (File f : files) {
//对遍历得到的File对象f进行判断,判断是否是文件夹
if(f.isDirectory()){
//f是一个文件夹,则继续遍历这个文件夹
getAllFile(f);
}else{
//f是一个文件,直接打印即可
System.out.println(f);
}
}
}
}
// 测试类2
public class Demo02Filter {
public static void main(String[] args) {
File file = new File("D:\\workspace");
getAllFile(file);
}
public static void getAllFile(File dir){
//传递过滤器对象 使用匿名内部类
File[] files = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
//过滤规则,pathname是文件夹或者是.java结尾的文件返回true
return pathname.isDirectory() ||
pathname.getName().toLowerCase().endsWith(".java");// 保留以java结尾的文件
}
});
for (File f : files) {
//对遍历得到的File对象f进行判断,判断是否是文件夹
if(f.isDirectory()){
//f是一个文件夹,则继续遍历这个文件夹
getAllFile(f);
}else{
//f是一个文件,直接打印即可
System.out.println(f);
}
}
}
}
IO:
按流向分类:
1.输入流:数据从硬盘流向代码内存
2.输出流:数据从代码内存流向硬盘
按数据类型分类
1.字节流(byte):以字节为单位,读写数据的流
2.字符流(char):以字符为单位,读写数据的流
四大顶级父类

InputStream:定义了一些读取操作
OutputStream:定义了一些写入操作
图片和视频使用字节流操作
读写文件的流
FileInputStream:InputStream的子类,用于从文件中读取字节
public static void fisDemo() throws IOException {
// 所读的文件必须要存在,若不存在,则会产生异常
FileInputStream fis = new FileInputStream("second_stage\\io\\abc.txt");
// 读出来的是int值,因为读取的时候是一个字节一个字节读取的
char read = (char)fis.read();
System.out.println(read);
// 第一种
// 循环读取文件内容,读到文件末尾返回-1
int b;
while ((b = fis.read()) != -1) {
// 打印一次后,跳过n位不读
fis.skip(2);
System.out.print((char)b);
}
// 第二种
// 最快读取文件的方法
byte[] c = new byte[1024];
fis.read(c);// 一次读取1024个字节
String s = new String(c);// 以字符串形式输出
System.out.println(s);
fis.close();
}
FileOutputStream:OutputStream的子类,用于将数据写入到文件
public static void fosDemo() throws IOException {
FileOutputStream fos;
// 创建文件输出流时,需要使用一个File对象来创建,如果当前文件不存在,就会创建一个新文件,如果存在,就会用新文件覆盖
// 如果File对象是一个文件夹的话就会报异常
fos = new FileOutputStream("second_stage\\io\\abc.txt",true);// 加上true后则不会覆盖,而是追加写入
// 一个字符占用两个字节,但字母只占用一个字节,汉字占用两个字节
// 写入一个字节
fos.write(97);
// 一次写入一个byte数组
// 写入中文时,不建议使用字节流
fos.write("国".getBytes());
// 从byte的第off位写到len位
fos.write("HelloWorld".getBytes(),1,4);
// 用完流之后要关闭
fos.close();
}
FileReader:字符读取流,和字节流基本一样,但字符流每次读出也都是以char为单位,一个字符一个字符的读出
public static void frDemo() throws IOException {
FileReader fr = new FileReader("second_stage\\io\\abc.txt");
// 第一种
// 循环遍历
int read = 0;
while ((read = fr.read()) != -1) {
System.out.print((char)read);
}
// 第二种
char[] c = new char[1024];
fr.read(c);
System.out.println(s);
fis.close();
}
例
// 读取文件,并判断文件中“花”字出现的次数
public static void main(String[] args) {
File file = new File("second_stage\\src\\test\\text.txt");
try {
FileReader reader = new FileReader(file);
BufferedReader bufferedReader = new BufferedReader(reader);
int count = 0;
String temp = "";
//一行一行读取内容,返回字符串
while ((temp = bufferedReader.readLine())!=null) {
//将字符串转换成char数组
char[] chars = temp.toCharArray();
// 遍历char数组
for (char aChar : chars) {
if (aChar == '花') {
count++;
}
}
}
System.out.println(count);
reader.close();
bufferedReader.close();
} catch (Exception e) {
e.printStackTrace();
}
}
FileWriter:字符输入流,用法和字节流基本一样。但字符流每次写入都是以char为单位,不会出现字符写一半,字符流可以直接写入String字符串。必须关闭流,否则不会写入,因为存在一个缓冲区(减少IO流的频率,增加文件的读写效率),也可以使用flush方法,将缓冲区的内容写入文件
IO次数:按一个字节一个字节的复制文件时,假如一张图片大小为1024字节,读取时,需要1024次读完,再通过1024次将图片写到文件夹中,整个操作的IO次数为2048次
缓冲流
提供了一个能够读取大量字节数据的容器,因此读写次数减少,IO次数减少,效率提升
字节缓冲流:
输入流:BufferedInputStream
使用:new BufferedInputStream(InputStream in) 传入一个InputStream类型的变量
输出流:BufferedOutputStream
使用:new BufferedOutputStream(OutputStream in) 传入一个OutputStream类型的变量
// 从一个文件夹中复制一张图片到另外一个文件夹中。 图片只能用字节流 图片的底层是二进制
public static void imgTest1() throws IOException {
FileInputStream fis = new FileInputStream("second_stage\\io\\imgA\\agt.jpg");
FileOutputStream fos = new FileOutputStream("second_stage\\io\\imgB\\agt.jpg");
// 创建缓冲流
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
int data;
// 将所读文件存入到data中,然后写入
while ((data = bis.read()) != -1) {
bos.write(data);
}
bis.close();
bos.close();
}
字符缓冲流
序列化流
序列化操作(写):对象-->字节-->存储到文件中
new ObjectOutputStream(OutputStream out) 传入一个OutputStream类型的变量
public static void main(String[] args) throws IOException {
Student student = new Student("张三",18);// 创建的对象类需要实现Serializable接口
ObjectOutputStream ops = new ObjectOutputStream(new FileOutputStream("second_stage\\io\\imgA\\123.txt"));
ops.writeObject(student);
ops.close();
}
反序列化操作(读):文件-->对象
new ObjectInputStream(InputStream out) 传入一个InputStream类型的变量
public static void read() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("second_stage\\io\\imgA\\123.txt"));
Student student = (Student) ois.readObject();
System.out.println(student);
ois.close();
}
将一个对象成功序列化后,如果将类信息改变,此时无法反序列,因为实现Serializable接口后,在每次对象序列化时,提供一个序列化id(serialVersionUID),在改变类后,序列号会重新提供,此时反序列化时,会导致序列号不匹配
解决:在类中手动声明一个id,public static final long serialVersionUID = 12345;再重新序列化后,就可以使用
Redis数据库:以key:value存储数据,(序列化操作)存储到本地文件中(可存储HashMap,List,Object)
反射
一个类需要加载编译后才可执行,即将类封装成一个Class对象,并将类的各部分(成员变量,构造方法,成员方法)进行存储
传统:new 对象——>获取类的信息
反射:拿到类的Class对象——>获取类的所有信息
创建类的Class对象:
1.Class.forName(类的路径);(常用)
// 反射必须要创建Class对象,可用于获取成员变量,构造方法和成员方法,当需要给成员变量赋值和调用方法时,就需要利用Class对象创建具体对象
Class people = Class.forName("People");
// newInstance() 利用Class对象创建People对象
People o = (People)people.newInstance();
// 根据反射获取类中的成员变量
people.getField(String name);// 根据名字获取public修饰的变量
people.getFields();// 返回类中public修饰的成员变量
people.getDeclaredField(String name);// 根据名字获取变量
people.getDeclaredField();// 返回类中所有成员变量
Field[] declaredFields = people.getDeclaredFields();// 获取类中所有的成员变量
for (Field declaredField : declaredFields) {
System.out.println(declaredField.getName());
}
// 使用反射给People对象的成员变量赋值
People p = (People) people.newInstance();
//给对象的name赋值,(1)获取到name变量(2)给指定对象的name赋值
//暴力反射,用以给私有变量赋值
name.setAccessible(true);
// set(对象,值)
name.set(p,"张三");// 表示给p对象的name赋张三
// get(对象)
System.out.println(name.get(p));// 获取
// 使用反射获取所有的构造方法
Constructor[] constructor = people.getConstructors();
for (Constructor constructor1 : constructor) {
System.out.println(constructor1);
}
// 使用无参构造创建对象
// (1)获取到无参构造(2)调用newInstance方法创建对象
Constructor constructor1 = people.getConstructor();
People o = (People)constructor1.newInstance();
// 使用有参构造创建对象
// (1)获取到有参构造(2)调用newInstance方法创建对象
Constructor constructor2 = people.getConstructor(String.class, int.class, char.class);
constructor2.newInstance("张三",11,'男');
// 获取成员方法
//无参
Method m = people.getMethod("eat");
// invoke(对象) 执行方法,通过指定对象调用方法
m.invoke(o);
// 有参
Method m2 = people.getMethod("eat",String.class);
m2.invoke(o,"肉");
2.类名.class
Class people1 = People.class;
3.对象.getClass(); (不常用)
Class people2 = new People().getClass();
Spring框架(反射+设计模式):
IOC:管理对象的容器
配置文件中写入类的路径,通过IO读取文件,拥有类的路径后就可以通过反射创建对象,再存储 到HashMap中,类名为key,对象为值,再通过getBean拿到对象
IOCTest类
//模拟IOC
//1.创建一个文本文件,编写要创建的对象的类路径
//2.在IOCTest类中,通过IO流读取文件中的信息(字符缓冲流)
//3.通过反射创建类的对象
//4.将对象存储到HashMap中,以类名为key,以对象为value进行存储
public class IOCTest {
// 存储对象的map
static HashMap<String,Object> map = new HashMap<String, Object>();
// 静态代码块,类加载时运行,在类加载时就把初始化工作完成
static {
try {
// 创建了一个字符缓冲输入流
BufferedReader bufferedReader = new BufferedReader(new FileReader("second_stage\\reflect\\src\\moon\\IOCTest.txt"));
// 读取文件中的内容
String path = "";
while ((path = bufferedReader.readLine()) != null) {// readLine(); 一行一行地读
// 通过反射创建对象
Class<?> c = Class.forName(path);// Class字节码对象
Object obj = c.newInstance();// 创对象
// 以类名作为key,对象为value存储到map中
//path.substring(path.lastIndexOf(".") + 1 因为路径最后一个点后面就是类名,所以用subString进行截取
map.put(path.substring(path.lastIndexOf(".") + 1), obj);//subString(start,end)对字符串进行截取 lastIndexOf(str)获取指定字符串最后一次出现下标
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 编写一个通过类名获取对象的方法
public static Object getBean(String name) {
if (map.get(name) == null) {
System.out.println("没有该对象");
return null;
}
return map.get(name);
}
}
文本文件(类的路径)
moon.People
测试类
public static void main(String[] args) {
//获取People对象
People p = (People) IOCTest.getBean("People");
System.out.println(p);
}
注解
也称为元数据,和类,接口,枚举在同一层次
可以将注解声明在包,类,变量,方法上,用于对这些元素进行说明
常见注解
1.@Override:用于方法上,表示该方法是重写父类的方法
2.@Deprecated:用于方法上,表示该方法已经过时,但可以继续使用
3.@SuppressWarning("all"):用于类上,表示取消所有代码警告
自定义注解
属性:注解中的属性为抽象方法,抽象方法的返回类型可以是(1)基本数据类型(2)String(3)枚举(4)数组
//自定义注解
public @interface AnnoDemo {
// 返回值为int
int age();
// 返回值为String
String name();
// 返回值为数组
String[] hobbies();
}
// 使用
@AnnoDemo(age = 18,name = "张三",hobbies = {"唱","跳"})
// 设置默认值后,在使用时可以不赋值
public @interface AnnoDemo {
// 返回值为int
int age() default 0;
// 返回值为String
String name() default "";
// 返回值为数组
String[] hobbies() default {};
}
// 使用
@AnnoDemo()
1.用于类上的注解
//作用于类上的注解
@Target(value = ElementType.TYPE)// 表示该注解作用于类上
@Retention(RetentionPolicy.RUNTIME)// 表示在运行时加载该注解,否则会报空指针异常(自定义注解必须加)
public @interface ClassAnno {
}
2.用于方法上的注解
//作用于方法上的注解
@Target(value = ElementType.METHOD)// 表示该注解作用于方法上
@Retention(RetentionPolicy.RUNTIME)// 表示在运行时加载该注解,否则会报空指针异常(自定义注解必须加)
public @interface MethodAnno {
}
3.用于变量上的注解
//作用于变量上的注解
@Target(value = ElementType.FIELD)// 表示该注解作用于变量上
@Retention(RetentionPolicy.RUNTIME)// 表示在运行时加载该注解,否则会报空指针异常(自定义注解必须加)
public @interface FieldAnno {
}
4.多种作用域
//作用于变量,方法,类上的注解
@Target(value = {ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)// 表示在运行时加载该注解,否则会报空指针异常(自定义注解必须加)
public @interface AllAnno {
}
获取注解上的属性值(反射)
类上
//注解
@AnnoDemo(age = 18,name = "张三",hobbies = {"唱","跳"})
public class NoteTest {
}
//获取类注解上的属性值
public class NoteTest {
public static void main(String[] args) {
// 1.创建类上字节码Class对象
Class<NoteTest> c = NoteTest.class;
// 2.获取类上注解
AnnoDemo annotation = c.getAnnotation(AnnoDemo.class);
// 3.获取属性
System.out.println(annotation.name());
}
}
变量上
//注解
public class NoteTest {
@FieldAnno(type = true)
int i;
}
//获取类中变量注解上的属性值
public class NoteTest {
public static void main(String[] args) {
// 1.创建类上字节码Class对象
Class<NoteTest> c = NoteTest.class;
// 2.通过字节码对象获取到变量
Field f = c.getDeclaredField("i");
// 3.获取变量上注解
FieldAnno annotation = f.getAnnotation(FieleAnno.class);
// 4.获取属性
System.out.println(annotation.type());
}
}
方法上
//注解
@MethodAnno(times = 10)
public static void test() {
}
//获取类中方法注解上的属性值
public class MethodAnnoTest {
public static void main(String[] args) throws NoSuchMethodException {
// 1.创建类上字节码Class对象
Class<MethodAnnoTest> c = MethodAnnoTest.class;
// 2.通过字节码对象获取到方法
Method m = c.getMethod("test");
// 3.获取方法上注解
MethodAnno methodAnno = m.getAnnotation(MethodAnno.class);
// 4.获取属性
System.out.println(methodAnno.times());
}
}
网络编程
是指多台设备之间,通过计算机网络进行连接并进行数据传输
网络编程三要素:
(1)协议:设备之间通过网络进行通讯时遵守的规则
1.TCP(在线发送):规定了设备之间必须连接过后才能传输数据

2.UDP(离线发送):设备之间不需要进行连接就能传输数据。是一个不可靠协议
(2)IP地址:在同一网络上,作为设备的唯一标识(手机有电话号码)
(3)端口号:作为设备中的应用程序运行的唯一标识
其他设备可通过 IP:端口号 对程序进行访问
Java实现TCP程序
包含服务端和客户端
步骤:
(1)服务端先启动,等待客户端连接
(2)客户端需要通过IP和端口号连接服务端
服务端的实现:
ServerSocket类:实现了服务端的套接字
// 客户端显示文字
public class Server {
public static void main(String[] args) {
// 需要在finally中关闭,所以需要在外面声明
ServerSocket serverSocket = null;
Socket socket = null;
OutputStream out = null;
try {
//1.创建了服务端套接字对象,并指定端口号为8081
serverSocket = new ServerSocket(8081);
//2.accept(); 等待接收客户端连接
socket = serverSocket.accept();
System.out.println("有客户端连接了");
//3.客户端连接服务端后,服务端响应数据给客户端
//(1).通过socket获取输出流
out = socket.getOutputStream();
//(2).响应给浏览器的固定设置
out.write("HTTP/1.1 200 OK\r\n".getBytes());
out.write("Content-Type:text/html\r\n".getBytes());
out.write("\r\n".getBytes());
//(3).往浏览器写数据
out.write("<h1 style='color:red'>hello</h1>".getBytes());
out.write("<a href='http://www.baidu.com'>hello</a>".getBytes());
out.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (serverSocket != null) {
serverSocket.close();
}
if (socket != null) {
socket.close();
}
if (out != null) {
out.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
// 客户端显示图片
public class PictureServer {
public static void main(String[] args) throws Exception{
//1.创建服务器套接对象
ServerSocket server = new ServerSocket(8081);
//2.通过accept()方法接收客户端连接
Socket socket = server.accept();
//3.获取输出流
OutputStream out = socket.getOutputStream();
out.write("HTTP/1.1 200 OK\r\n".getBytes());
out.write("Content-Type:image/jpeg\r\n".getBytes());// 读取文字和图片不同
out.write("\r\n".getBytes());
//4,通过字节流读取图片
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("second_stage\\network\\1.png"));
int data;
while ((data = bis.read()) != -1) {
out.write(data);
}
//5.关闭资源
server.close();
socket.close();
out.close();
bis.close();
}
}
客户端的实现:
Socket类:实现了客户端的套接字
public class Chat {
public static void main(String[] args) throws IOException {
//创建客户端套接字对象。并指定连接服务器端的ip和端口号
Socket socket = new Socket("localhost",8081);
socket.close();
}
}
聊天案例
// 服务器
public static void main(String[] args) throws IOException {
//1.创建服务端套接字对象
ServerSocket server = new ServerSocket(8081);
while (true) {
//2.调用accept()等待客户端连接,将客户端封装成Socket对象
Socket socket = server.accept();
System.out.println("客户端连接成功");
//二.服务端接收消息
//3.通过Socket获取字节输入流,获取客户端发来的消息
InputStream in = socket.getInputStream();
byte[] bytes = new byte[2048];// 一个UTF-8中文字符占用3个字节
//4.将输入流读取到的字节数据存储到字节数组中
in.read(bytes);
//5.将字节数组转换成字符串
System.out.println(new String(bytes));
//三.服务端发消息
//6.通过Socket获取字节输出流,给客户端回消息
OutputStream out = socket.getOutputStream();
System.out.println("请输入消息:");
String message = new Scanner(System.in).next();
out.write(("服务端:"+message).getBytes());
}
}
// 客户端
public static void main(String[] args) throws IOException {
while (true) {
// 一.客户端发消息
//1.创建客户端套接字对象,并指定要连接的服务端的ip和端口号
Socket socket = new Socket("localhost",8081);
//2.获取Socket对象提供的字节输出流,给服务端发送消息
OutputStream out = socket.getOutputStream();
//3.从控制台输入消息
System.out.println("请输入消息:");
String message = new Scanner(System.in).next();
//4.通过输出流将消息写到服务端
out.write(("客户端:"+message).getBytes());// 通过getBytes将字符转换成字节
//四.客户端接受消息
//5.从Socket对象中获取字节输入流,获取服务端发来的消息
InputStream in = socket.getInputStream();
byte[] bytes = new byte[2048];
in.read(bytes);
System.out.println(new String(bytes));
}
}
Java1.8的新特性
Lambda表达式
实现接口的方式:使用Lambda表达式(该接口只能有一个抽象方法)
函数式接口:只包含一个抽象方法的接口称为函数式接口,lambda表达式实现的接口都是函数式接口。可以用@FunctionalInterface注解来标注该接口为函数式接口
Lambda表达式的常见格式
public static void main(String[] args) {
// 箭头左边是接口中抽象方法要传递的参数
// 箭头右边是返回的值或表达式
//1.
Calculate calculate = (int num1, int num2) -> num1 + num2;
System.out.println(calculate.cal(1,2));
//2.
Calculate calculate1 = (a,b)->a+b;
System.out.println(calculate1.cal(2,3));
//3.
Calculate calculate2 = (a, b) -> {
System.out.print("结果是:");
return a + b;
};
System.out.println(calculate2.cal(3,4));
//4.
int cal = ((Calculate) (x, y) -> x + y).cal(4, 5);
System.out.println(cal);
}
Stream
主要用于集合数据操作,对集合中数据进行统计等操作
1.将list转换成set
public static void main(String[] args) {
//将list转换成set
ArrayList<String> list = new ArrayList<>();
list.add("富强");
list.add("民主");
list.add("文明");
Set<String> collect = list.stream().collect(Collectors.toSet());
System.out.println(collect);
}
2.将list转换成map
public static void main(String[] args) {
//将list转换成map
ArrayList<People> people1 = new ArrayList<>();
people1.add(new People("张三",18,'男'));
people1.add(new People("李四",18,'男'));
people1.add(new People("王五",18,'女'));
//将People的name作为key,将People的年龄作为value
//Collectors.toMap(Function fun1,Function fun2) 第一个参数是设置key值,第二个参数是设置value值
//将People的name作为Key值,name是String类型,即泛型的第二个参数
Function<People,String> function = peo->{
return peo.getName();
};
Function<People,Integer> function1 = peo->{
return peo.getAge();
};
Map<String, Integer> collect = people1.stream().collect(Collectors.toMap(function, function1));
System.out.println(collect);
}
设计模式
单例模式
在整个程序的运行过程中,保证实例化的对象只有一个,不能手动创建对象,对象由类方法提供,即自行实例化并向整个系统提供这个实例
饿汉式单例:对象在 类加载时 就创建好了
设计:
1.声明一个静态类型对象引用,并进行实例化
2.将构造方法用private修饰,避免在其它类中随意创建对象
3.编写一个返回对象的方法,用public修饰,供给调用者使用
public class Singleton {
// 声明一个静态类型对象引用,并进行实例化
private static Singleton singleton = new Singleton();
// 将构造方法用private修饰
private Singleton() {
}
// 编写一个返回对象的方法,用public修饰
public static Singleton getInstance() {
return singleton;
}
}
懒汉式单例:对象在调用方法时才会被创建
设计:
1.不进行初始化
2.将构造方法用private修饰,避免在其它类中随意创建对象
3.编写一个返回对象的方法,用public修饰,判断当对象为空时才创建
public class LazySingleton {
// 不进行初始化
private static LazySingleton lazySingleton;
// 将构造方法用private修饰
private LazySingleton() {
}
// 编写一个返回对象的方法,用public修饰,判断当对象为空时才创建
// 初次调用时为空,则会创建对象,待到第二次调用时,由于已经存在了实例对象,则会直接返回第一次创建的对象
public static LazySingleton getInstance() {
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
饿汉式和懒汉式区别:
1.饿汉式对象在类加载时被创建,如果该对象不使用,则会造成资源浪费
2.懒汉式线程不安全,可以用public synchronized static修饰方法来避免
工厂模式
工厂生成产品:
1.产品说明书(父类)
2.产品角色(通过说明书设计出来的产品)(子类)
3.工厂(根据客户需求来提供产品,产品就是对象)
简单工厂模式
产品说明书
public class Car {
private String name;
private String color;
public Car() {
}
public Car(String name, String color) {
this.name = name;
this.color = color;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
'}';
}
}
产品角色
public class BMW extends Car {
public BMW() {
super("华晨宝马","白色");
}
}
public class Benci extends Car {
public Benci() {
super("奔驰","黑色");
}
}
工厂
public class CarFactory {
public Car createCar(String name) {
switch (name) {
case "BMW":
return new BMW();
case "Benci":
return new Benci();
default:
System.out.println("没有该车");
return null;
}
}
}
用户
public class Test {
public static void main(String[] args) {
//1.创建工厂
CarFactory carFactory = new CarFactory();
//2.获取BMW对象
Car bmw = carFactory.createCar("BMW");
System.out.println(bmw);
}
}
工厂方法模式
产品说明书和产品角色同上
工厂
public interface CarFactory {
// 造车的方法
Car createCar();
}
具体工厂
public class BMWFactory implements CarFactory {
@Override
public Car createCar() {
return new BMW();
}
}
public class BenciFactory implements CarFactory {
@Override
public Car createCar() {
return new Benci();
}
}
测试
public class Test {
public static void main(String[] args) {
CarFactory factory = new BMWFactory();
Car car = factory.createCar();
System.out.println(car);
}
}
代理模式
静态代理
对象类
public class House {
private int price = 1000;
public House() {
}
public House(int price) {
this.price = price;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
代理类
public class HouseProxy {
public House getHouse() {
// 代理对象需要拿到目标对象才能进行包装
House house = new House();
// 代理对对象进行一些拓展,修改和增强操作
house.setPrice(house.getPrice()*2);
// 再将对象返回,此时通过代理获取的对象就是被修改后的对象
return house;
}
}
测试类
public class Test {
public static void main(String[] args) {
// 创建代理类
HouseProxy proxy = new HouseProxy();
// 通过代理获取被代理修改后的对象
House house = proxy.getHouse();
System.out.println(house.getPrice());
}
}
动态代理
接口的实现类
public class UserServiceImpl implements UserService{
@Override
public void login(String password) {
System.out.println(password);
System.out.println("执行登录...");
}
@Override
public void register() {
System.out.println("执行注册...");
}
@Override
public void logout() {
System.out.println("执行退出...");
}
}
测试类
@Test
public void test02() {
// 使用Proxy创建一个代理类 反射
// 第一个参数为:类加载器,用于加载代理类对象
// 第二个参数为:被代理的类所实现的接口
// 第三个参数为:代理执行器,指定被代理的对象以及执行的方式(新建类,并实现InvocationHandler接口)
UserService service = (UserService) Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(),
UserServiceImpl.class.getInterfaces(),
new ProxyHandler(new UserServiceImpl()));//创建代理类
service.login("123456");// 在调用方法时,不是直接调用类中的方法,而是调用代理类中的方法
service.register();
service.logout();
}
代理类
// 编写代理的业务逻辑代码,即代理所要做的事
public class ProxyHandler implements InvocationHandler {
// 声明被代理对象
Object object;
// 初始化被代理对象
public ProxyHandler(Object object) {
this.object = object;
}
// 反射
// proxy:指定方法是哪个对象的
// method:封装了被代理类执行的方法
// args:方法的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 调用方法,使用反射进行调用
System.out.println(LocalDateTime.now());
if (args != null) {
String string = args[0].toString();// 在参数中获取password值
string = "654321";// 修改
args[0] = string;// 又赋给参数
}
Object obj = method.invoke(object, args);// 可以看做Object类携带参数args执行了method方法
return obj;
}
}

8286

被折叠的 条评论
为什么被折叠?



