集合:
ArrayList的特点:
1.List接口的实现类
2.通常可以根据下标进行操作
3.元素有下标,有序,可重复
4.底层结构是数组,内存空间是连续的
5.增删操作比较慢,查询操作比较快 ( 前提是数据量比较大时 )
6.初始容量为10,扩容以1.5倍扩容
ArrayList示例:
package cn.tedu.collection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
/**本类用于ArrayList的相关测试*/
public class TestArrayList {
public static void main(String[] args) {
//1.创建对应的集合对象
/*底层会自动帮我们们创建数组来存放对象,并且数组的初始容量是10*/
ArrayList<Integer> list = new ArrayList<>();
//2.向集合中添加元素,用于测试
list.add(100);
list.add(200);
list.add(300);
list.add(400);
list.add(300);
list.add(400);
System.out.println(list);
//3.测试常用方法
// list.clear();
// System.out.println(list);//[]
System.out.println(list.contains("100"));//false,"100"是String类型,是否包含指定元素"100"
System.out.println();
System.out.println(list.get(0));//100,取集合下标为0的元素
System.out.println(list.indexOf(400));//3,判断元素第一次出现的下标
System.out.println(list.lastIndexOf(400));//5.判断元素最后一次出现的下标
/*下面的代码报错,数组下标越界,index:300 size:6
* 主要是因为List中有两个重载的remove()方法,如果传入的是300
* 会直接把这个300看作是int类型的index索引,所以如果想指定元素来删除数据
* 需要把int类型的300手动装箱成Integer类型的300元素*/
//System.out.println(list.remove(300));//报错,下标越界
System.out.println(list);
System.out.println(list.remove(Integer.valueOf(300)));//手动装箱,根据具体元素删除内容,参数为引用类型,int需要手动装箱
System.out.println(list);
System.out.println(list.remove(2));//根据下标删除元素
System.out.println(list);
System.out.println(list.set(2,777));//修改指定位置处的元素为777
System.out.println(list);
System.out.println(list.size());
System.out.println(list);
//测试集合间的操作
ArrayList<Integer> list2 = new ArrayList<>();
list2.add(1);
list2.add(2);
list2.add(3);
list2.add(4);
System.out.println(list2);//将list2集合中的所有元素添加到list集合的末尾
list.addAll(list2);//[100,200,777,400,1,2,3,4]
System.out.println(list);
list.addAll(0,list2);//将list2集合中的所有元素从list集合的0号位置开始添加
System.out.println(list);//[1,2,3,4,100,200,777,400,1,2,3,4]
list.removeAll(list2);//删除list集合中所有属于list2集合的元素
System.out.println(list);//[100,200,777,400]
list.add(1);
System.out.println(list.containsAll(list2));//list集合中是否包含list2的所有元素
list.retainAll(list2);//取交集,保留list中属于list2的所有元素
System.out.println(list);//[1]
ArrayList<Integer> list3 = new ArrayList<>();
list3.add(1);
list3.add(2);
list3.add(3);
list3.add(4);
//测试集合的迭代
/*1.通过for循环
* 2.高效for循环
* 3.iterator
* 4.listIterator*/
//方式1:因为List集合是有序的,元素是有下标的,所以可以根据下标进行遍历
//从哪开始:0 到哪结束:list.size()-1 如何变化:++
for (int i = 0; i < list3.size(); i++) {
System.out.println(list3.get(i));
}
System.out.println("========");
//方式2:因为普通for循环遍历效率低,语法复杂,所以使用高效for循环来遍历
//格式:for(每轮遍历到的元素的类型 元素名:要遍历的元素){循环体}
for(Integer n:list3){
System.out.println(n);
}
System.out.println("========");
//方式3:从父接口Collection中继承过来的迭代器
//1.获取要迭代的集合对应的迭代器
Iterator<Integer> it = list3.iterator();
//2.通过迭代器判断是否有下一个元素可以迭代
while (it.hasNext()){
//3.通过迭代器获取本轮迭代的元素
System.out.println(it.next());
}
System.out.println("=======");
/*方式4:ListIterator属于List接口独有的迭代器
Iterator<E>---父接口,常用方法hasNext()和next()
* ListIterator<E>----子接口---可以使用父接口的功能,除了父接口的功能
之外还有自己的特有功能,比如逆序遍历,添加元素等,但注意,不常用
public interface ListIterator<E> extends Iterator<E>*/
ListIterator<Integer> it2 = list3.listIterator();
while(it2.hasNext()){
System.out.println(it2.next());
}
System.out.println("==========");
for (int i = list3.size()-1; i >=0 ; i--) {
System.out.println(list3.get(i));
}
}
}
LinkedList:
LinkedList的特点:
1.List接口的实现类
2.元素有下标,有序,可重复
3.底层的数据结构是链表,内存空间不连续的
4.通过进行首尾节点的操作比较多
5.增删操作比较快,查询操作比较慢[数据量比较大时]
注意:LinkedList的查询操作也不是都慢,首尾操作还是比较快的
package cn.tedu.collection;
import java.util.LinkedList;
/**本类用于LinkedList的相关测试*/
public class TestLinkedList {
public static void main(String[] args) {
//1.创建集合对象
LinkedList<String> li = new LinkedList<>();
//2.添加数据
li.add("孙悟空");
li.add("猪八戒");
li.add("唐三藏");
li.add("沙师弟");
li.add("白龙马");
System.out.println(li);
//测试LinkedList的独有方法
li.addFirst("蜘蛛精");//添加首元素
li.addLast("玉兔精");//添加尾元素
System.out.println(li);//[蜘蛛精, 孙悟空, 猪八戒, 唐三藏, 沙师弟, 白龙马, 玉兔精]
System.out.println(li.getFirst());//获取首元素,蜘蛛精
System.out.println(li.getLast());//获取尾元素,玉兔精
System.out.println(li.removeFirst());//删除首元素
System.out.println(li);//[孙悟空, 猪八戒, 唐三藏, 沙师弟, 白龙马, 玉兔精]
System.out.println(li.removeLast());//删除尾元素
System.out.println(li);//[孙悟空, 猪八戒, 唐三藏, 沙师弟, 白龙马]
/*别名:查询系列*/
System.out.println(li.element());//孙悟空,获取首元素
System.out.println(li.peek());//孙悟空,获取首元素
System.out.println(li.peekFirst());//孙悟空,获取首元素
System.out.println(li.peekLast());//白龙马,获取尾元素
/*别名:新增系列*/
System.out.println(li.offer("海绵宝宝"));//添加尾元素
System.out.println(li);
System.out.println(li.offerFirst("派大星"));//添加首元素
System.out.println(li);
System.out.println(li.offerLast("蟹老板"));//添加尾元素
System.out.println(li);
/*别名:移除系列*/
System.out.println(li.poll());//删除首元素
System.out.println(li.pollFirst());//删除首元素
System.out.println(li.pollLast());//删除尾元素
System.out.println(li);
}
}
Map集合:
- Map可以根据键来提取对应的值
- Map的键不允许重复,如果重复,对应的值会被覆盖
- Map存放的都是无序的数据
- Map的初始容量是16,默认的加载因子是0.75
常用方法:
void clear() 从此映射中移除所有映射关系(可选操作) boolean containsKey(Object key) 如果此映射包含指定键的映射关系,则返回 true
boolean containsValue(Object value) 如果此映射将一个或多个键映射到指定值,则返回 true
Set<Map.Entry<K,V>> entrySet() 返回此映射中包含的映射关系的 Set 视图
boolean equals(Object o) 比较指定的对象与此映射是否相等
V get(Object key) 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null
int hashCode() 返回此映射的哈希码值
boolean isEmpty() 如果此映射未包含键-值映射关系,则返回 true
Set keySet() 返回此映射中包含的键的 Set 视图
V put(K key, V value) 将指定的值与此映射中的指定键关联(可选操作)
void putAll(Map<? extends K,? extends V> m)从指定映射中将所有映射关系复制到此映射中(可选操作)
V remove(Object key) 如果存在一个键的映射关系,则将其从此映射中移除(可选操作)
int size() 返回此映射中的键-值映射关系数
Collection values() 返回此映射中包含的值的 Collection 视图
Map的常用方法与迭代:
package cn.tedu.map;
import java.util.*;
/**本类用于测试Map接口*/
public class MapDemo {
public static void main(String[] args) {
//1.创建集合Map对象
/*Map中的数据要符合映射规则,一定注意要同时指定K和V的数据类型
* 至于这个K和V具体要设置成什么类型,取决于具体的业务需求*/
Map<Integer,String> map = new HashMap<>();
//2.向map集合存入数据,注意方法是put()方法,并且需要存入一对的<K,V>的值
map.put(9527,"白骨精");
map.put(9528,"黑熊精");
map.put(9529,"锂鱼精");
map.put(9530,"黄毛怪");
map.put(9527,"女儿国国王");
map.put(9531,"黑熊精");
/*1.Map中的K不允许重复,如果重复,后面的value会把前面的value覆盖掉
* 比如:女儿国国王和白骨精,的key都是9527,白骨精就被覆盖了
* 2.Map中的value可以重复,比如我们有两个黑熊精*/
System.out.println(map);
//3.进行方法测试
//map.clear();//清空数据
//System.out.println(map);//{}
System.out.println(map.isEmpty());//false,判断集合是否为空
System.out.println(map.size());//获取集合中键值对的对数
System.out.println(map.containsKey(9527));//true,判断集合是否包含9527的key
System.out.println(map.containsValue("白骨精"));//false,判断集合是否包含指定的值value
System.out.println(map);
System.out.println(map.remove(9529));//根据key删除对应的这一对键值对
Collection<String> c = map.values();//将Map中所有的value值取出,存入Collection中
System.out.println(c);
//4.map集合的迭代方式1
/*Map的迭代方式1:
* 遍历map中的数据,但是map本身没有迭代器,所以需要先转换成set集合
* 用到的方法:keySet():把map中的所有key值存入到set集合中,返回值Set<Key>
*/
//4.1将map集合的所有key值取出,存入Set集合中,Set集合的泛型就是key的类型
Set<Integer> set = map.keySet();
//4.2迭代Set集合,首先获取迭代器
Iterator<Integer> it = set.iterator();
//4.3循环,判断是否有下一个元素可以迭代
while(it.hasNext()){
//4.4获取本轮循环到的元素,就是set中存的每个key
Integer key = it.next();
//4.5根据key获取map中的value值
String value = map.get(key);
System.out.println("{"+key+","+value+"}");//拼接打印
}
/*Map的迭代方式2:
* 遍历Map集合,需要把map集合先转成set集合
* 是把map中的一对健值对key&value作为一个Entry<K,V>整体放入set
* 一对K,V就是一个Entry*/
//将Map集合的Entry转成Set集合
Set<Map.Entry<Integer,String>> set2 = map.entrySet();
//获取Set集合的迭代器
Iterator<Map.Entry<Integer, String>> it2 = set2.iterator();
//循环,判断是否有下一个元素可以迭代
while (it2.hasNext()){
Map.Entry<Integer, String> entry = it2.next();//获取Set集合的元素,每个元素都是一个Entry健值对
System.out.println(entry);//打印Set集合元素Entry值
Integer key = entry.getKey();//获取Entry的健
String value = entry.getValue();//获取Entry的值
System.out.println("{"+key+","+value+"}");//拼接打印
}
}
}
Set集合:
- Set是一个不包含重复数据的Collection
- Set集合中的数据是无序的(因为Set集合没有下标)
- Set集合中的元素不可以重复 – 常用来给数据去重
集合Set的常用方法和迭代器:
package cn.tedu.collection;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**本类用于测试Set*/
public class TestSet {
public static void main(String[] args) {
//1.创建对应的集合对象
Set<String> set = new HashSet<>();
set.add("紫霞仙子");
set.add("至尊宝");
set.add("蜘蛛精");
set.add("紫霞仙子");
/*1.Set集合中的元素都是没有顺序的
* 2.Set集合中的元素不能重复
* */
System.out.println(set);
//3.常用方法
System.out.println(set.contains("唐僧"));//false,判断是否包含指定元素
System.out.println(set.isEmpty());//false,判断集合是否为空
System.out.println(set.remove(null));//true,移除集合中的元素null
System.out.println(set);//[蜘蛛精, 至尊宝, 紫霞仙子]
System.out.println(set.size());//3,获取集合中的元素个数
//把Set集合转为数组,然后用Arrays.toString()查看数组元素
System.out.println(Arrays.toString(set.toArray()));//[蜘蛛精, 至尊宝, 紫霞仙子]
//4.测试集合间的
Set<String> set2 = new HashSet<>();
set2.add("小兔纸");
set2.add("小脑斧");
set2.add("小海藤");
set2.add("小狮几");
System.out.println(set2);
//将集合set中的所有元素添加到set2集合中
set2.addAll(set);
System.out.println(set2);
//判断set集合中是否包含set2的所有元素
System.out.println(set.containsAll(set2));//false
//移除集合set2中属于集合set中的所有元素
System.out.println(set2.removeAll(set));
System.out.println(set2);
//取set2集合中属于set集合的公共元素
System.out.println(set2.retainAll(set));
System.out.println(set2);//[]
//5.集合的迭代器
//获取迭代器
Iterator<String> it = set.iterator();
//循环迭代,判断是否有下一个元素可以迭代
while(it.hasNext()){
//打印循环迭代到的元素
System.out.println(it.next());
}
}
}
测试Set集合存对象:
package cn.tedu.collection;
import java.util.HashSet;
import java.util.Set;
/**本类用于测试Set存放自定义引用类型对象的去重问题*/
public class TestSet2 {
public static void main(String[] args) {
//1.创建Set集合对象
Set<Student> set = new HashSet<>();
//2.创建Student类对象
Student s1 = new Student("张三",3);
Student s2 = new Student("李四",4);
Student s3 = new Student("王五",5);
Student s4 = new Student("王五",5);
set.add(s1);
set.add(s2);
set.add(s3);
set.add(s4);
/*如果Set集合中存放的是我们自定义的类型
那么需要给自定义类中添加重写的equals()与hashCode()方法,
才有会实现去重的效果
不然,Set集合认为s3与s4的地址值不同,是两个不同的对象,不会去重*/
System.out.println(set);
//需求:定义小狗类Dog,将小狗类的对象存入Set集合
//实现以类型 属性 属性值去重的效果
Set<Dog> dogset = new HashSet<>();
Dog d1 = new Dog("伍","旺",3);
Dog d2 = new Dog("tg","旺旺",4);
Dog d3 = new Dog("rrr","旺旺旺",5);
Dog d4 = new Dog("rrr","旺旺旺",5);
dogset.add(d1);
dogset.add(d2);
dogset.add(d3);
dogset.add(d4);
System.out.println(dogset);
}
}
class Student {
//2.创建属性
String name;//姓名
int id;//学号
//3.提供全参构造
public Student(String name, int id) {
this.name = name;
this.id = id;
}
//4.添加重写的toString()方法
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", id=" + id +
'}';
}
//5.添加重写的equals()和hashCode()方法,比较对象的类型+属性+属性值
@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 &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, id);
}
}
class Dog {
String host;
String name;
int age;
public Dog() {}
public Dog(String host, String name, int age) {
this.host = host;
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"host='" + host + '\'' +
", name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Dog dog = (Dog) o;
return age == dog.age &&
Objects.equals(host, dog.host) &&
Objects.equals(name, dog.name);
}
@Override
public int hashCode() {
return Objects.hash(host, name, age);
}
}
Collection:集合层次的根接口
List:有序,有下标,可以重复
ArrayList:底层结构是数组,内存空间连续,常用于下标操作
LinkedList:底层结构是链表,
Set:无序,无下标,不可以重复
Map:<K,V> key&value 键值对 映射关系 Entry
Map.Entry<K,V>:内部类的应用
map中的key不允许重复.
package cn.tedu.review;
import java.util.*;
/*本类用于练习Map案例,统计字符串中字符的个数
* 需求效果:用户输入aabbcc,输出{a=2,b=2,c=2}*/
public class TestMap {
public static void main(String[] args) {
//1.提示并接收用户输入的字符串
System.out.println("请您输入要统计的字符串:");
String input = new Scanner(System.in).nextLine();
//2.准备一个map集合,用来存放出现的字符与字符的次数
//也就是说字符不允许重复,但字符出现的次数可以重复
//也就是说字符不允许重复,但字符出现的次数可以重复
Map<Character,Integer> map = new HashMap<>();
//3.准备向map中存入数据
//3.1遍历用户输入的字符串,拿到每个字符
for (int i = 0; i < input.length(); i++) {
char key = input.charAt(i);
//System.out.println(key);//打印查看每轮循环获取到的字符,没有问题
//3.2根据刚刚获取到的key拿到对应的value
Integer value = map.get(key);
if(value == null){//之前这个字符没有出现过,次数还是Integer的默认值null
map.put(key,1);//没出现过,次数就设置为1
}else{//value不是null,说明之前已经存过次数了
map.put(key,++value);//出现过,次数就设置为之前的次数+1
}
}
System.out.println("各个字符出现的次数为:"+map);
}
}
进程,线程:
进程就是正在运行的程序,它会占用对应的内存区域,由CPU进行执行与计算。
进程的特点:
独立性
进程是系统中独立存在的实体,它可以拥有自己独立的资源,每个进程都拥有自己私有的地址空间,在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间
动态性
进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合,程序加入了时间的概念以后,称为进程,具有自己的生命周期和各种不同的状态,这些概念都是程序所不具备的.
并发性
多个进程可以在单个处理器CPU上并发执行,多个进程之间不会互相影响.
线程的概念:
线程是操作系统OS能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.
一个进程可以开启多个线程,其中有一个主线程来调用本进程中的其他线程。
我们看到的进程的切换,切换的也是不同进程的主线程
多线程可以让同一个进程同时并发处理多个任务,相当于扩展了进程的功能。
进程与线程的关系:
每个线程在共享同一个进程中的内存的同时,又有自己独立的内存空间.
所以想使用线程技术,得先有进程,进程的创建是OS操作系统来创建的,一般都是C或者C++完成
什么是进程?什么是程序?有什么区别?
程序:数据与指令的集合,程序是静态的
进程:给程序加入了时间的概念,是动态的,代表是操作系统OS中正在运行的程序,具有独立性,动态性,并发性
什么是线程?线程和进程有什么关系?
线程是操作系统OS能够进行运算调度的最小单位
一个进程可以有多个线程,也可以只有一个线程
只有一个线程的进程称作单线程程序
CPU:电脑的核心处理器,类似于人的大脑
多线程的特性:
我们宏观上觉得多个进程是同时运行的,但实际的微观层面上,一个CPU【单核】只能执行一个进程中的一个线程。
那为什么看起来像是多个进程同时执行呢?
是因为CPU以纳秒级别甚至是更快的速度高效切换着,超过了人的反应速度,这使得各个进程从看起来是同时进行的,也就是说,宏观层面上,所有的进程看似并行【同时运行】,但是微观层面上是串行的【同一时刻,一个CPU只能处理一件事】。
串行是指同一时刻一个CPU只能处理一件事,类似于单车道
并行是指同一时刻多个CPU可以处理多件事,类似于多车道
时间片,即CPU分配给各个线程的一个时间段,称作它的时间片,即该线程被允许运行的时间,如果在时间片用完时线程还在执行,那CPU将被剥夺并分配给另一个线程,将当前线程挂起,如果线程在时间片用完之前阻塞或结束,则CPU当即进行切换,从而避免CPU资源浪费,当再次切换到之前挂起的线程,恢复现场,继续执行。
注意:我们无法控制OS选择执行哪些线程,OS底层有自己规则,如:
FCFS(First Come First Service 先来先服务算法)
SJS(Short Job Service短服务算法)
线程的三态:
由于线程状态比较复杂,我们由易到难,先学习线程的三种基础状态及其转换,简称”三态模型” :
就绪(可运行)状态:线程已经准备好运行,只要获得CPU,就可立即执行
执行(运行)状态:线程已经获得CPU,其程序正在运行的状态
阻塞状态:正在运行的线程由于某些事件(I/O请求等)暂时无法执行的状态,即线程执行阻塞
就绪 → 执行:为就绪线程分配CPU即可变为执行状态"
执行 → 就绪:正在执行的线程由于时间片用完被剥夺CPU暂停执行,就变为就绪状态
执行 → 阻塞:由于发生某事件,使正在执行的线程受阻,无法执行,则由执行变为阻塞
(例如线程正在访问临界资源,而资源正在被其他线程访问)
反之,如果获得了之前需要的资源,则由阻塞变为就绪状态,等待分配CPU再次执行
我们可以再添加两种状态:
- 创建状态:线程的创建比较复杂,需要先申请PCB,然后为该线程运行分配必须的资源,并将该线程转为就绪状态插入到就绪队列中
- 终止状态:等待OS进行善后处理,最后将PCB清零,并将PCB返回给系统
线程生命周期,主要有五种状态:
新建状态(New) : 当线程对象创建后就进入了新建状态.如:Thread t = new MyThread();
就绪状态(Runnable):当调用线程对象的start()方法,线程即为进入就绪状态.
处于就绪(可运行)状态的线程,只是说明线程已经做好准备,随时等待CPU调度执行,并不是执行了t.start()此线程立即就会执行
运行状态(Running):当CPU调度了处于就绪状态的线程时,此线程才是真正的执行,即进入到运行状态
就绪状态是进入运行状态的唯一入口,也就是线程想要进入运行状态状态执行,先得处于就绪状态
阻塞状态(Blocked):处于运状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入就绪状态才有机会被CPU选中再次执行.
根据阻塞状态产生的原因不同,阻塞状态又可以细分成三种:
等待阻塞:运行状态中的线程执行wait()方法,本线程进入到等待阻塞状态
同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),它会进入同步阻塞状态
其他阻塞:调用线程的sleep()或者join()或发出了I/O请求时,线程会进入到阻塞状态.当sleep()状态超时.join()等待线程终止或者超时或者I/O处理完毕时线程重新转入就绪状态
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期
多线程编程实现方案1 ( 创建类继承多线程类Thread ) :
以多线程的方式运行程序:
package cn.tedu.thread;
/*本类用于多线程编程实现方案1:继承Thread类的方式来完成*/
public class TestThread1 {
public static void main(String[] args) {
//4.创建线程对象进行测试
/*4.new对应的是线程的新建状态*/
/*5.要想模拟多线程,至少得启动2个线程
* 如果只启动1个线程,是单线程程序*/
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
MyThread t4 = new MyThread();
/*6.这个run()方法如果直接这样调用,是没有多线程抢占执行的效果的
* 这个写法只是把代码看作是普通方法来调用,谁先调用,谁先执行*/
/*7.start()对应的状态就是就绪状态,会把刚刚新建好的线程加入到就绪队列之中
* 至于这个线程什么时候执行,这就是多线程执行的随机性,需要等待操作系统OS第二名并分配CPU
* 8.线程的执行具有随机性,也就是说t1-t4具体怎么执行
* 取决于Cpu的调度与时间片的分配,我们是决定不了的
* 9.执行start()方法时,底层会自动调用我们重写的,包含自己业务的run()方法*/
// t1.run();
// t2.run();
// t3.run();
// t4.run();
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//1.自定义一个多线程类
class MyThread extends Thread{
/*1.多线程编程实现方案1:继承Thread类+重写run()*/
@Override
public void run() {
/*2.super.run()表示的是调用父类的业务,我们现在想用自己的业务
* 所以注释掉*/
//super.run();
//3.完成自己的业务:打印10次当前正在执行的线程名称
for (int i = 0; i < 10; i++) {
/*3.getName()表示可以获取当前正在执行的线程名称
* 由于本类继承了Thread类,所以可以直接使用Thread类的这个方法*/
System.out.println(i+"="+getName());
}
}
}
多线程编程实现方案2 ( 创建类实现多线程接口Runnable ) :
以多线程的方式运行程序:
package cn.tedu.thread;
/**本类用于多线程编程实现方案2:实现Runnable接口来完成*/
public class TestThread2 {
public static void main(String[] args) {
//4.创建自定义对象---目标业务类对象
MyRunnable target = new MyRunnable();
//5.创建多个线程对象
/*如何以多线程的方式启动线程?需要调用start()方法
* 自己没有,需要与Thread类建立联系:Thread(Runnable target)
* 但是由于Runnable是接口,无法实例化,我们可以把接口实现类对象
* 也就是我们的自定义类的对象作为构造方法的参数传入,与Thread类建立关系*/
Thread t1 = new Thread(target);
Thread t2 = new Thread(target);
Thread t3 = new Thread(target);
Thread t4 = new Thread(target);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//1.自定义多线程类
class MyRunnable implements Runnable{
//2.添加接口中未实现的抽象方法,用于完成业务
@Override
public void run() {
//3.写业务:打印10次当前正在执行的线程名称
for (int i = 0; i <1000; i++) {
/*问题:自定义类与父接口Runnable中都没有获取线程名字的方法
* 所以还需要从Thread类中找:
* currentThread():静态方法,由刚刚获取到的线程对象调用,返回当前线程对象的名字*/
System.out.println(i+"="+Thread.currentThread().getName());
}
}
}
多线程的实现方案总结:
方案1:直接继承多线程父类 extends Thread
1.定义自己的多线程类,并且继承Thread
2.重写父类的run()方法,里面在是我们自己的业务
3.创建自定义线程类对象
4.通过线程对象调用start()方法,将线程加入到就绪队列中
5.运行程序查看多线程抢占资源的效果
方案2:实现多线程接口 implements Runnable
1. 定义自己的业务类,并实现接口Runnable
2.在业务类中添加接口里的抽象方法run(),并实现业务
3.创建唯一的业务类对象
4.创建多个Thread类的对象,作为多个线程对象,并将刚刚的业务对象作为参数,传入Thread类的构造方法中Thread(Runnable target)
5.使用多个线程类对象调用start()方法,将线程加入到就绪队列之中
6.查看多线程抢占资源的效果
方案2的优点:
1.没有继承,耦合性不强,后续扔然可以继承或者实现其它的接口,比较自由
2.可以给所有的线程对象统一业务,业务只需要发布一次,保持统一
3.面向接口进行编程,代码更加高级
设计多线程模型,4个售票窗口共计售票100张:
方式一:继承Thread
package cn.tedu.tickets;
/**需求:设计多线程模型,4个售票窗口共计售票100张
* 本方案使用多线程编程实现方案1:继承Thread类的方式来完全*/
public class TestThread {
public static void main(String[] args) {
TicketThread t1 = new TicketThread("窗口1");
TicketThread t2 = new TicketThread("窗口2");
TicketThread t3 = new TicketThread("窗口3");
TicketThread t4 = new TicketThread("窗口4");
//4.以多线程的方式启动线程对象
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//1.自定义多线程售票类,继承Thread
class TicketThread extends Thread{
//3.定义成员变量,用来保存票数,全类共享
/*问题:4.线程对象共计售票400张,原因是创建了4个本类的对象,各自操作各自的成员变量
* 解决:让所有对象共享同一个变量,这个变量需要设置为静态的*/
//int tickets = 100;
static int tickets = 100;
/*添加本类的含参构造,用于给线程对象起名字
* 由于只添加了这一个含参构造,默认的无参构造被覆盖掉了*/
public TicketThread(String name) {
/*表示调用父类的含参构造,因为实际上起名的功能是父类完成的
* 所以我们需要将本构造方法接到的名字参数,传给父类的构造方法,让父类起名字*/
super(name);
}
//2.重写run()方法,里面是我们的业务
@Override
public void run() {
//4.1循环卖票
while (true){
//打印当前正在卖票的线程名称,并且票数-1
System.out.println(getName()+"="+tickets--);
//4.3做判断,如果没有票了,就退出死循环
if(tickets<=0) break;//注意!死循环必须设置出口!
//如果if的{}里的语句只有一句,大括号可以省略不写
}
}
}
方式二:实现接口Runnable
package cn.tedu.tickets;
/**需求:设计多线程模型,4个窗口共计售票100张
* 本方案使用多线程编程方案2,实现Runnable接口的方式来完成*/
public class TestRunnable {
public static void main(String[] args) {
//5.创建自定义类的对象,并将唯一的业务对象作为参数传入
TicketsRunnable target = new TicketsRunnable();
//6.创建多个线程对象
Thread t1 = new Thread(target,"窗口1");
Thread t2 = new Thread(target,"窗口2");
Thread t3 = new Thread(target,"窗口3");
Thread t4 = new Thread(target,"窗口4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//1.定义一个TicketRunnable类实现Runnable接口
class TicketsRunnable implements Runnable{
//3.定义变量,保存票数
/*由于自定义的类对象,作为唯一的业务对象,只new了一次
* 所以发布的任务,也就是票数天然的会被所有线程对象共享,无需设置为静态也只有一份*/
int tickets = 100;
//2.添加父接口未实现的方法,里面写我们自己的业务
@Override
public void run() {
//4.循环卖票
while (true){
System.out.println(Thread.currentThread().getName()+"="+tickets--);
if(tickets<=0) break;
}
}
}
同步锁:
我们如何判断程序有没有可能出现线程安全问题,主要有以下三个条件:
在多线程程序中 + 有共享数据 + 多条语句操作共享数据
同步与异步:
同步:体现了排队的效果,同一时刻只能有一个线程独占资源,其他没有权利的线程排队。
坏处就是效率会降低,不过保证了安全。
异步:体现了多线程抢占资源的效果,线程间互相不等待,互相抢占资源。
坏处就是有安全隐患,效率要高一些。
synchronized同步关键字:
synchronized (锁对象) {undefined
需要同步的代码(也就是可能出现问题的操作共享数据的多条语句);
}
同步效果的使用有两个前提:
前提1:同步需要两个或者两个以上的线程(单线程无需考虑多线程安全问题)
前提2:多个线程间必须使用同一个锁(我上锁后其他人也能看到这个锁,不然我的锁锁不住其他人,就没有了上锁的效果)
同步线程锁 ( 锁必须得唯一 )
双重校验机制,解决卖票机超卖重卖 问题:
package cn.tedu.review;
/**本类用于复写售票案例实现方案2*/
public class TestRunnable {
public static void main(String[] args) {
//4.创建自定义类对象,作为唯一的业务对象
TicketRunnable target = new TicketRunnable();
//5.创建多个线程对象,并且把唯一的对象交给线程对象来干活
Thread t1 = new Thread(target, "窗口1:");
Thread t2 = new Thread(target, "窗口2:");
Thread t3 = new Thread(target, "窗口3:");
Thread t4 = new Thread(target, "窗口4:");
//6.以多线程方式启动程序
t1.start();
t2.start();
t3.start();
t4.start();
}
}
/*多线程出现数据安全问题的原因:多线程程序+共享数据+多条语句操作共享数据*/
/*解决方案 同步锁:相当于给容易出现问题的代码加了一把锁,包裹了所有可能会出现数据安全的代码
* 加锁之后,就有了同步(排队)的效果,加锁需要考虑:
* 锁的范围:不能太大,太大了,干什么都要排队,效率低;
* 不能太小,太小了,锁不住,还是会出现安全隐患
* 并且注意!!!多个线程间,必须使用同一把锁,否则锁不住!!!*/
//2.自定义多线程售票类
class TicketRunnable implements Runnable {
//3.定义变量,票数100
int tickets = 100;
Object o = new Object();
Dog d =new Dog();
//2.添加接口中未实现的方法,里面是我们自己的业务
@Override
public void run() {
//3.循环卖票
while (true) {
/*同步代码块:synchronized(唯一的锁对象){会出现安全隐患的所有代码块}*/
/*同步代码块在同一时刻,同一资源只会被一个线程对象独享*/
/*这种写法不对,相当于每个线程进来时,执行这句话都会new一个新的锁对象
* 多个线程间使用的不是同一个唯一的锁对象,所以锁不住*/
//synchronized (new Object()) {
/*修改同步代码块的锁对象为成员变量o,因为锁对象必须唯一*/
synchronized (d) {//同步代码块解决的是重卖的问题
//7.让线程休眠,增加出错概率
if(tickets>0){//如果有票,就卖票(双重校验)
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + tickets--);
}
if (tickets <= 0) break;
}
}
}
}
class Dog{}
锁是唯一的,类的字节码文件也是唯一的,直接用类的字节码文件名作为同步锁 ( 改善 )
双重校验机制,解决卖票机超卖重卖 问题:
package cn.tedu.review;
/**本类用于复写售票案例实现方案2*/
public class TestRunnable {
public static void main(String[] args) {
//4.创建自定义类对象,作为唯一的业务对象
TicketRunnable target = new TicketRunnable();
//5.创建多个线程对象,并且把唯一的对象交给线程对象来干活
Thread t1 = new Thread(target, "窗口1:");
Thread t2 = new Thread(target, "窗口2:");
Thread t3 = new Thread(target, "窗口3:");
Thread t4 = new Thread(target, "窗口4:");
//6.以多线程方式启动程序
t1.start();
t2.start();
t3.start();
t4.start();
}
}
/*多线程出现数据安全问题的原因:多线程程序+共享数据+多条语句操作共享数据*/
/*解决方案 同步锁:相当于给容易出现问题的代码加了一把锁,包裹了所有可能会出现数据安全的代码
* 加锁之后,就有了同步(排队)的效果,加锁需要考虑:
* 锁的范围:不能太大,太大了,干什么都要排队,效率低;
* 不能太小,太小了,锁不住,还是会出现安全隐患
* 并且注意!!!多个线程间,必须使用同一把锁,否则锁不住!!!*/
//2.自定义多线程售票类
class TicketRunnable implements Runnable {
//3.定义变量,票数100
int tickets = 100;
//Object o = new Object();
//Dog d =new Dog();
//2.添加接口中未实现的方法,里面是我们自己的业务
@Override
public void run() {
//3.循环卖票
while (true) {
/*同步代码块:synchronized(唯一的锁对象){会出现安全隐患的所有代码块}*/
/*同步代码块在同一时刻,同一资源只会被一个线程对象独享*/
/*这种写法不对,相当于每个线程进来时,执行这句话都会new一个新的锁对象
* 多个线程间使用的不是同一个唯一的锁对象,所以锁不住*/
//synchronized (new Object()) {
/*修改同步代码块的锁对象为成员变量o,因为锁对象必须唯一*/
synchronized (TicketRunnable.class) {//同步代码块解决的是重卖的问题
//7.让线程休眠,增加出错概率
if(tickets>0){//如果有票,就卖票(双重校验)
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + tickets--);
}
if (tickets <= 0) break;
}
}
}
}
//class Dog{}
创建线程池的工具类对象Executors
常用方法是newFixedThreadPool(int)这个方法可以创建指定数目的线程池对象
创建出来的线程池对象是ExecutorService:这个池子用来新建/启动/存储/销毁线程
package cn.tedu.review;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**本类用于复写售票案例实现方案2*/
public class TestRunnable {
public static void main(String[] args) {
//4.创建自定义类对象,作为唯一的业务对象
TicketRunnable target = new TicketRunnable();
//5.创建多个线程对象,并且把唯一的对象交给线程对象来干活
// Thread t1 = new Thread(target, "窗口1:");
// Thread t2 = new Thread(target, "窗口2:");
// Thread t3 = new Thread(target, "窗口3:");
// Thread t4 = new Thread(target, "窗口4:");
//6.以多线程方式启动程序
// t1.start();
// t2.start();
// t3.start();
// t4.start();
/*Executors是用来辅助创建线程池的工具类对象
* 常用方法是newFixedThreadPool(int)这个方法可以创建指定数目的线程池对象
* 创建出来的线程池对象是ExecutorService:这个池子用来新建/启动/存储/销毁线程*/
ExecutorService pool = Executors.newFixedThreadPool(5);//参数5是指线程池最多不超过5个线程
for (int i = 0; i < 5; i++) {
/*execute()方法是让线程池中的线程来执行业务,每次调用这个方法
* 都会将一个线程加入到就绪队列中,但注意,具体执行还得看操作系统OS是否选中*/
//本方法的参数是Runnable接口对象,就是我们实现类写的业务,也就是业务类对象target
pool.execute(target);
}
}
}
/*多线程出现数据安全问题的原因:多线程程序+共享数据+多条语句操作共享数据*/
/*解决方案 同步锁:相当于给容易出现问题的代码加了一把锁,包裹了所有可能会出现数据安全的代码
* 加锁之后,就有了同步(排队)的效果,加锁需要考虑:
* 锁的范围:不能太大,太大了,干什么都要排队,效率低;
* 不能太小,太小了,锁不住,还是会出现安全隐患
* 并且注意!!!多个线程间,必须使用同一把锁,否则锁不住!!!*/
//2.自定义多线程售票类
class TicketRunnable implements Runnable {
//3.定义变量,票数100
int tickets = 100;
//Object o = new Object();
//Dog d =new Dog();
//2.添加接口中未实现的方法,里面是我们自己的业务
@Override
public void run() {
//3.循环卖票
while (true) {
/*同步代码块:synchronized(唯一的锁对象){会出现安全隐患的所有代码块}*/
/*同步代码块在同一时刻,同一资源只会被一个线程对象独享*/
/*这种写法不对,相当于每个线程进来时,执行这句话都会new一个新的锁对象
* 多个线程间使用的不是同一个唯一的锁对象,所以锁不住*/
//synchronized (new Object()) {
/*修改同步代码块的锁对象为成员变量o,因为锁对象必须唯一*/
synchronized (TicketRunnable.class) {//同步代码块解决的是重卖的问题
//7.让线程休眠,增加出错概率
if(tickets>0){//如果有票,就卖票(双重校验)
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + tickets--);
}
if (tickets <= 0) break;
}
}
}
}
//class Dog{}
注解:
package cn.tedu.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**类用于完成自定义注解*/
public class TestAnnotation {
}
//2.通过@Target注解标记自定义注解可以使用的位置
/*3.通过元注解@Target规定自定义注解可以使用的位置
* 我们使用"ElementType.静态常量"的方式来指定自定义注解可以加在什么位置
* 而且,值可以写多个,格式@Target({ElementType.XXX1,ElementType.XXX2})*/
@Target({ElementType.METHOD, ElementType.TYPE})
//3.通过@Retention注解标记自定义注解的生命周期
@Retention(RetentionPolicy.RUNTIME)
/*4.通过元注解@Retention规定自定义注解的生命周期
* 我们使用"RetentionPolicy.静态常量"的方式来指定自定义注解的生命周期
* 注意:值只能写一个:SOURCE CLASS RUNTIME 3选1*/
//1.定义自定义注解
/*1.首先注意:注解定义的语法与java不同
* 2.定义自定义注解的格式:@interface 注解名*/
@interface Rice{
//5.我们可以给注解进行功能增强--添加注解的属性
/*6.注意:int eat();不是方法的定义,而是给自定义注解添加了一个age属性*/
//int age();//给自定义注解添加了一个普通属性age,类型为int
int age() default 0;//给自定义注解的普通属性赋予默认值0
/*7.注解中还可以添加特殊属性value
* 特殊属性的定义方式与普通属性一样,主要是使用方式不同
* 注意:特殊属性的名字必须叫value,但是类型不做限制
* 特殊属性也是可以赋予默认值的,格式与普通属性的赋值一样,不能简写*/
//String value();//定义一个特殊属性value,类型是String
String value() default "Lemon";//定义特殊属性,并且给特殊属性赋予默认值
}
/*5.注解使用的格式:@注解名*/
//4.定义一个类,用来测试自定义注解
//@Rice
class TestAnno{
/*测试1:分别给TestAnno类 name属性 eat()方法上都添加Rice注解
* 结论:属性上的注解报错,说明自定义注解可以加在什么位置,由@Target决定*/
//@Rice
String name;
/*测试2:当我们给Rice注解添加了一个age属性以后,@Rice注解使用时直接报错
* 结论:当注解没有定义属性时,可以直接使用
* 当注解定义了属性以后,必须给属性赋值,格式:@Rice(age = 10)*/
//@Rice(age = 10)
/*测试3:给age属性赋予默认值以后,可以直接使用@Rice注解
* 不需要给age属性赋值,因为age属性已经有默认值0了*/
/*测试4:给Rice注解添加了特殊属性value以后,也必须给属性赋值
* 只不过特殊属性赋值时可以简写成:@Rice("Apple")*/
/*测试5:如果特殊属性也赋予了默认值,那么可以直接使用这个注解
* 如果想要给注解的所有属性赋值,每条赋值都不能简写*/
//@Rice("Apple")
//@Rice
@Rice(age = 10,value = "orange")
public void eat(){
System.out.println("eat方法");
}
}
package cn.tedu.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class DemoTest {
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.FIELD,ElementType.TYPE})
@interface Cat{
String name() default "";
int value() default 0;
}
@Cat(name="tom")
class Test{
@Cat(666)
String host;
@Cat(name="jack",value=999)
public void eat(){
}
}
设计模式:
单例模式:可以说是大多数开发人员在实际中使用最多的,常见的Spring默认创建的bean就是单例模式的。
单例模式有很多好处,比如可节约系统内存空间,控制资源的使用。
其中单例模式最重要的是确保对象只有一个。
简单来说,保证一个类在内存中的对象就一个。
底层的实现思路一共分为了3个步骤:
对本类构造方法私有化,防止外部调用构造方法创建对象
创建全局唯一的对象,也做私有化处理
通过自定义的公共方法将创建好的对象返回(类似封装属性后的getXxx() )
单例设计模式方案1:饿汉式
package cn.tedu.design;
/**本类用于实现单例设计模式方案1*/
public class Singleton1 {
public static void main(String[] args) {
//new Mysingle();//构造方法私有化后,外面不可以用
MySingle s1 = MySingle.getSingle();
MySingle s2 = MySingle.getSingle();
System.out.println(s1==s2);//true,只new了一次对象,静态资源全类共享
}
}
//1.创建自己的单例程序
class MySingle{
//2.提供本类的构造方法,并将构造方法私有化
/*1.构造方法私有化的目的:为了防止外界随意调用,创建本类对象*/
private MySingle(){}
//3.创建本类的对象
//4.2由于静态资源只能调用静态资源,所以single对象也需要设置成静态
static private MySingle single = new MySingle();
//4.提供公共的访问方式,返回刚刚创建好的对象
//4.1为了不通过对象,直接调用本方法,需要将本方法设置为静态
static public MySingle getSingle(){
/*后续可以添加权限校验*/
return single;
}
}
Day17复习:
注解的快速回顾
package cn.tedu.review;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**本类用于注解的快速回顾*/
public class TestAnnotation {
}
//3.自定义注解一直到字节码文件中都生效
@Retention(RetentionPolicy.CLASS)
//2.自定义注解可以作用于属性与类
@Target({ElementType.TYPE,ElementType.FIELD})
//1.定义自定义注解Rice
@interface Rice{
//4.自定义注解拥有普通属性char gender
char gender() default '女';
//5.自定义注解拥有特殊属性String name
String value() default "枪哥yyds";
}
//6.准备测试类
@Rice(gender = '男',value = "枪哥yyds")
class TestAnno{
//@Rice
//@Rice(gender = '男')
@Rice(value = "詹姆斯·高斯林yyds")
String kind;
public void eat(){
System.out.println("eat()");
}
}
单例模式方案1饿汉式回顾:
package cn.tedu.review;
/**本类用于复写单例设计模式方案1*/
public class Singleon1 {
public static void main(String[] args) {
Mysingle s1 = Mysingle.getSingle();
Mysingle s2 = Mysingle.getSingle();
System.out.println(s1==s2);
System.out.println(s1);
System.out.println(s2);
}
}
//1.创建自己的单例模式
class Mysingle{
//2.提供构造方法,并封装私有化
private Mysingle(){}
//3.创建本类的对象,将对象的地址值交给引用类型的变量来保存
private static Mysingle single = new Mysingle();
//4.提供一个公共的访问方式,向外界提供创建好的唯一的本类对象
public static Mysingle getSingle(){
return single;
}
}
单例模式方案2:懒汉式
package cn.tedu.design;
/**本类用于实现单例设计模式方案2:懒汉式*/
/*关于单例模式的两种实现方式:
* 1.饿汉式:不管你用不用这个类的对象,都会直接先创建一个
* 2.懒汉式:先不给你创建这个类的对象,等你需要的时候再创建---延迟加载的思想
* 延迟加载的思想:是指不会在第一时间就把对象创建好占用内存,
* 而是什么时候用到,什么时候再去创建对象
* 3.线程安全问题:由于我们存在唯一的对象single2,并且多条语句都操作了这个变量
* 所以,如果将程序放在多线程的环境下,就容易出现数据安全问题
* 解决方案:
* 1)将3条语句都使用同步代码块包裹,保证同步排队的效果
* 2)由于getSingle()方法里也只有这3条语句,所以也可以将本方法修饰为同步方法
* 注意:被synchronized修饰的方法称作同步方法,但是不推荐使用!!!*/
public class Singleton2 {
public static void main(String[] args) {
Mysingle2 s1 = Mysingle2.getSingle();
Mysingle2 s2 = Mysingle2.getSingle();
System.out.println(s1==s2);
System.out.println(s1);
System.out.println(s2);
}
}
//1.创建自己的单例程序MySingle2
class Mysingle2{
//2.私有化本类的构造方法
private Mysingle2(){}
//3.创建的是本类对象对应类型的引用类型变量,用来保存对象的地址值
//singele2是本类的成员变量,引用类型的默认值是null
private static Mysingle2 single2;
//4.提供公共的静态方法,将创建好的对象返回
//synchronized public static Mysingle2 getSingle(){
public static Mysingle2 getSingle(){
synchronized (Mysingle2.class) {
if (single2 == null) {
single2 = new Mysingle2();
}
return single2;
}
}
}
反射:
Reflection(反射) 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,也有称作“自省”。
反射非常强大,它甚至能直接操作程序的私有属性。我们前面学习都有一个概念,被private封装的资源只能类内部访问,外部是不行的,但这个规定被反射赤裸裸的打破了。
反射就像一面镜子,它可以在运行时获取一个类的所有信息,可以获取到任何定义的信息(包括成员变量,成员方法,构造器等),并且可以操纵类的字段、方法、构造器等部分。
创建字节码对象的三种方式:
方式一:Class.forName(“类的全路径”);
方式二:类名.class
方式三:对象名.getClass()
获取包名 类名
clazz.getPackage().getName()//包名
clazz.getSimpleName()//类名
clazz.getName()//完整类名
获取成员变量定义信息
getFields()//获取所有公开的成员变量,包括继承变量
getDeclaredFields()//获取本类定义的成员变量,包括私有,但不包括继承的变量
getField(变量名)
getDeclaredField(变量名)
获取构造方法定义信息
getConstructor(参数类型列表)//获取公开的构造方法
getConstructors()//获取所有的公开的构造方法
getDeclaredConstructors()//获取所有的构造方法,包括私有
getDeclaredConstructor(int.class,String.class)
获取方法定义信息
getMethods()//获取所有可见的方法,包括继承的方法
getMethod(方法名,参数类型列表)
getDeclaredMethods()//获取本类定义的的方法,包括私有,不包括继承的方法
getDeclaredMethod(方法名,int.class,String.class)
反射新建实例
clazz.newInstance();//执行无参构造创建对象
clazz.newInstance(666,”海绵宝宝”);//执行含参构造创建对象
clazz.getConstructor(int.class,String.class)//获取构造方法
反射调用成员变量
clazz.getDeclaredField(变量名);//获取变量
clazz.setAccessible(true);//使私有成员允许访问
f.set(实例,值);//为指定实例的变量赋值,静态变量,第一参数给null
f.get(实例);//访问指定实例变量的值,静态变量,第一参数给null
反射调用成员方法
Method m = Clazz.getDeclaredMethod(方法名,参数类型列表);
m.setAccessible(true);//使私有方法允许被调用
m.invoke(实例,参数数据);//让指定实例来执行该方法
package cn.tedu.reflection;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
/**本类用于测试反射*/
public class TestReflect {
/*单元测试方法的格式:public + void + 无参 + @Test*/
//1.通过单元测试方法,获取目标类Student对应的字节码对象
@Test
public void getClazz() throws ClassNotFoundException {
//练习获取字节码对象的3种方式
Class<?> clazz1 = Class.forName("cn.tedu.reflection.Student");//方式一
Class<?> clazz2 = Student.class;//方式二
Class<?> clazz3 = new Student().getClass();//方式三
//打印获取到的字节码对象
System.out.println(clazz1);//class cn.tedu.reflection.Student
System.out.println(clazz2);//class cn.tedu.reflection.Student
System.out.println(clazz3);//class cn.tedu.reflection.Student
//通过刚刚获取到的字节码对象,获取Student类的资源
//获取字节码对象对应的全路径名
System.out.println(clazz1.getName());//cn.tedu.reflection.Student
//获取字节码对象对应的类名
System.out.println(clazz1.getSimpleName());//Student
//获取Student类对应的包对象
System.out.println(clazz1.getPackage());//package cn.tedu.reflection
//先获取Student类对应的包对象,再通过这个包对象,获取包名
System.out.println(clazz1.getPackage().getName());//cn.tedu.reflection
}
//2.通过单元测试方法,获取Student类中的成员变量
@Test
public void getFie() throws ClassNotFoundException {
//获取目标类对应的字节码对象
Class<?> clazz = Class.forName("cn.tedu.reflection.Student");
//通过字节码对象获取成员变量们
Field[] fs = clazz.getFields();
//打印数组,查看数组中的内容
System.out.println(Arrays.toString(fs));
//遍历数组
for(Field f:fs){
/*注意:目前成员变量的修饰符需要是public才能获取到,其它修饰符可以通过暴力反射获取*/
System.out.println(f);//打印本轮循环到的数组中的字段对象
System.out.println(f.getName());//通过字段对象获取本字段的名字
System.out.println(f.getType());//通过字段对象获取本字段的类型
}
}
//3.通过单元测试方法,获取Student类中的成员方法
@Test
public void getFuction(){
//1.获取目标类对应的字节码对象
Class<Student> clazz11 = Student.class;
//通过字节码对象获取目标类中的成员方法们
Method[] ms = clazz11.getMethods();
//打印查看方法数组对象的所有内容
System.out.println(Arrays.toString(ms));
//使用高效for循环遍历数组
for(Method m:ms){
System.out.println(m);
System.out.println(m.getName());//通过遍历到的方法对象,查看方法名
//通过遍历到的方法对象,查看方法的参数类型,返回值为数组
System.out.println(Arrays.toString(m.getParameterTypes()));
}
}
//4.通过单元测试方法,获取目标类Student中的所有构造方法们
@Test
public void getCons(){
//获取字节码对象
Class<?> clazz222 = new Student().getClass();
//通过字节码对象,获取目标类中的构造方法们
Constructor<?>[] c = clazz222.getConstructors();
//遍历构造方法数组,分别打印方法名和方法参数
for(Constructor n:c){
System.out.println(n.getName());
System.out.println(Arrays.toString(n.getParameterTypes()));
}
}
//5.通过反射,创建目标类的对象
@Test
public void getObject() throws Exception {
//获取字节码对象
Class<?> clazz = Student.class;
//通过反射技术创建目标类的对象
/*反射创建对象方案1:通过触发目标类中的无参构造创建对象*/
Object o = clazz.newInstance();
System.out.println(o);
/*反射创建对象方案2:通过触发目标类的全参构造创建对象*/
/*1.先获取指定的构造方法对象
* 注意需要指定构造方法的参数,并且传入的是.class字节码对象*/
//获取目标类中指定的那个全参构造对象
Constructor<?> cs = clazz.getConstructor(String.class,int.class);
System.out.println(cs);
//通过获取到的构造方法:创建对象+给对象的所有属性赋值
Object o1 = cs.newInstance("赵六", 6);
System.out.println(o1);
//需求:触发Student类中第3个构造方法,创建对象o3
//先获取到指定的构造方法对象
Constructor<?> cs11 = clazz.getConstructor(String.class, int.class, int.class);
//使用这个刚获取到的构造方法对象,帮我们创建Student类的对象
Object o12 = cs11.newInstance("王五", 6, 18);
System.out.println(o12);
}
}
class Student {
//1.定义成员变量
private String name;
public int age;
public int age2;
//2.给被封装的属性提供get与set方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//3.添加本类的无参构造与全参构造
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student(String name, int age,int age2) {
this.name = name;
this.age = age;
this.age2 = age2;
}
//4.提供本类的普通方法
public void play(){
System.out.println("今天大结局,放学后我要写1w行代码玩玩~");
}
public void eat(int n){
System.out.println("今天要吃"+n+"道好菜~");
}
//5.为了查看对象时打印具体的类型 属性 属性值 所以重写toString()方法
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", age2=" + age2 +
'}';
}
}
暴力反射:
package cn.tedu.reflection;
import org.junit.Test;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**本类用于测试暴力反射*/
public class TestReflection22 {
/*1.通过暴力反射获取与操作属性*/
@Test
public void getFie2() throws Exception {
//获取字节码对象
Class<Person> clazz = Person.class;
//获取指定的私有属性,方法参数传入的就是指定的属性的名字
Field field = clazz.getDeclaredField("name");
//打印查看刚刚获取到的属性对象
System.out.println(field);
//根据刚刚获取到的属性对象,进一步查看属性的各种信息
System.out.println(field.getType());//class java.lang.String
System.out.println(field.getType().getName());//java.lang.String
/*设置属性的值*/
//1.需要指定到底是哪个对象的name属性设置值,没有对象就创建对象
Person p1 = clazz.newInstance();//利用反射,触发无参构造创建对象
/*暴力反射,需要设置私有可见的权限!!!*/
field.setAccessible(true);
//通过字段对象Field给刚刚创建好的对象p1设置属性值
//用到的方法是:field.set(0,n),o是指具体给哪个对象的属性设置值,n是指具体的值是什么
field.set(p1,"海绵宝宝");
//field.get(o);o是指具体查看哪个对象的name属性值
System.out.println(field.get(p1));
}
//定义单元测试方法,利用暴力反射操作Person类中的私有属性age
@Test
public void getFie3() throws Exception {
//获取字节码对象
Class<Person> clazz = Person.class;
Field age = clazz.getDeclaredField("age");
System.out.println(age);
System.out.println(age.getType().getName());
//创建对应的对象
Person p2 = clazz.newInstance();
age.setAccessible(true);
age.set(p2,66);
System.out.println(age.get(p2));
}
/*2.通过暴力反射获取与使用方法*/
@Test
public void getFunction2() throws Exception {
//获取字节码对象
Class<?> classzz = Person.class;
//通过暴力反射获取指定的私有方法
/*getDeclaredMethod(n,x,y,z....)
* n:指要获取的指定方法的方法名
* x,y,z...可变参数,是指指定方法的参数类型,注意传入的是字节码对象.class*/
Method method = classzz.getDeclaredMethod("save", int.class, String.class);
//System.out.println(method);//查看是否获取到方法对象
//没有对象,就通过反射创建类的对象
Object p3 = classzz.newInstance();
method.setAccessible(true);
/*invoke(o,x,y,z...)表示通过反射执行方法
* o:指要给哪个对象执行上面获取到的方法,如save()
* x,y,z...执行方法时需要传入的参数,如save()方法的参数*/
method.invoke(p3,999,"感冒灵");
}
@Test
public void getFunction3() throws Exception {
//获取字节码对象
Class<?> clazz = Person.class;
//获取私有方法
Method me = clazz.getDeclaredMethod("update");
//通过字节码对象创建类的对象
Object p5 = clazz.newInstance();
//设置操作权限
me.setAccessible(true);
//执行方法
me.invoke(p5);
}
@Test
public void getFunction4() throws Exception {
//获取字节码对象
Class<?> clazz = new Person().getClass();
//获取私有方法对象
Method me = clazz.getDeclaredMethod("add", String.class);
me.setAccessible(true);
//创建类对象
Object p6 = clazz.newInstance();
//调用方法
me.invoke(p6,"好的");
}
}
class Person {
//1.提供私有属性
private String name;
private int age;
//2.提供私有方法
private void save(int n,String s){
System.out.println("save方法"+n+s);
}
private void update(){
System.out.println("update方法");
}
private void add(String str){
System.out.println("add方法"+str);
}
}
日期类Date的入门:
package cn.tedu.date;
import java.text.SimpleDateFormat;
import java.util.Date;
/**本类用于日期类Date的入门*/
public class TestDate {
public static void main(String[] args) {
//创建日期类对象,包含当前日期
Date d1 = new Date();
System.out.println(d1);
//创建一个日期转换工具类的对象,用来转换日期的格式
SimpleDateFormat sdf = new SimpleDateFormat();
//使用工具转换日期
String s = sdf.format(d1);
System.out.println(s);
//还可以自定义格式来转换日期
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String s2 = sdf2.format(d1);
System.out.println(s2);
}
}
日期类:LocalDate
package cn.tedu.jbbc;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.Set;
public class TestCalendar {
public static void main(String[] args) {
LocalDate now = LocalDate.now();
System.out.println(now);
LocalTime now1 = LocalTime.now();
System.out.println(now1);
LocalDateTime now2 = LocalDateTime.now();
System.out.println(now2);
// LocalTime.now(ZoneId.of())
Set<String> a = ZoneId.getAvailableZoneIds();
for(String i:a){
System.out.println(i);
}
LocalTime now3 = LocalTime.now(ZoneId.of("Hongkong"));
System.out.println(now3);
}
}