一、线程
1.1、线程的概念
-
程序就是一段代码
-
进程就是一个应用软件的执行
-
线程就是进程的一部分,一个进程可以启动多个线程
-
对于java程序来说,主启动就会启动jvm,也就是一个进程 。
-
然后jvm会启动一个主线程来调用main方法
-
同时启动一个垃圾回收线程,最简单的也有两个线程并发
1.2、线程简单的内存模型
在jdk一点八之前基本模型如下三个部分:
每个线程都会单开一个栈,开启之后会从第一个方法开始执行,将方法的栈帧压入栈中,然后方法中如果调用了新的方法会将这个新的方法的栈帧继续压入栈中。如果创建对象,数组等就会将这些对象创建在共享的堆内存中
-
栈:存放各个方法的栈帧
-
堆:用于存放对象实例、实例变量、数组、垃圾回收管理内存区域解决数据存储问题
-
方法区:存放类信息、常量池、静态变量等。
-
ps:方法区分为永久代和元空间,在jdk8之前方法区采用永久代实现,在jvm中存储,8之后由元空间实现,由操作系统管理,可以自动扩容解决了内存溢出的问题。
-
进程之间内存不共享,同进程中线程共享堆和方法区
线程的使用:
ps:使用集成类的方式的时候,创建线程对象可以直接创建自定义的类对象,或者使用java原有的线程对象,构造方法传递新的类对象;
但是使用实现接口的时候只能创建java原有的线程对象,然后new类传递进构造方法
第一种方式:继承Thread类(注意重写run方法)
public class thread_1 {
public static void main(String[] args) {
myThread myThread=new myThread();
// myThread.run(); 直接启动该方法的话就不会开启一个新的线程,会直接执行该方法
myThread.start();//调用该方法就会直接开启一个新的线程,然后java程序就会向下继续执行
//开启新的线程本质上就是开启一个新的栈空间,然后方法就结束了,启动
//成功的线程会自动调用run方法
for (int i=1;i<100;i++){
System.out.println("主线程"+i);
}
}
}
class myThread extends Thread{
@Override
public void run(){
for (int i=0;i<100;i++){
System.out.println("副线程"+i);
}
}
}
来说一下执行结果:主副线程交替不规则执行,说明jvm虚拟机再并发(两个线程一起执行,cpu在线程间切换)执行。
第二种方式:实现Runnable接口,实现run方法
然后直接创建线程对象的时候放入该类
该方法使用匿名内部类的形式直接实现接口之后放入线程对象的构造方法里
public static void main(String[] args) {
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
for (int i=0;i<100;i++){
System.out.println("副线程"+i);
}
}
});
thread.start();
for (int i=0;i<100;i++){
System.out.println("main线程"+i);
}
}
第二种方式更加实用,因为类可以实现多个接口,但是不可以集成多个类。
1.3、线程的生命周期
新建状态:刚创建出来的线程对象
调用start()↓
-
就绪状态:又称为可运行状态,表示当前线程具有抢夺cpu时间片的权利,当一个线程抢夺到时间片之后就会执行run方法,进入运行状态。
-
阻塞状态:当一个线程遇到语塞事件,例如需要睡眠或者输入。就会放弃占用的时间片,回到就绪之后需要重新抢夺。
-
运行状态:run方法的开始执行标志着线程运行,当占有的时间片用完之后就会重新回到就绪状态等待下次抢到cpu,运行完所有的程序之后会进入死亡状态
线程的睡眠和打断:
-
线程的睡眠一般使用sleep方法,
-
线程的打断可以使用直接关掉线程的stop,但是不推荐
-
或者使用触发异常信息的interrupt()方法
-
也可以在类中声明一个变量的形式来改变变量打断睡眠
package Thread;
public class Thread_sleep {
public static void main(String[] args) {
Thread thread=new Thread(new myrun());//创建线程对象
thread.start();//执行线程,开辟新的栈空间
//thread.currentThread()在这里获取的线程对象是主线程,哪怕使用的是副线程调用的。
for (int i=0;i<3;i++){
System.out.println("主线程"+i);
try {
Thread.sleep(1000);//执行一个停顿一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//thread.stop();//过时了,因为该方法会直接停掉该线程,然后会有语句无法执行
thread.interrupt();//让该线程的睡眠出错执行catch方法,这样达到终止睡眠的方法
}
}
class myrun implements Runnable{
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("实现Runnable"+ Thread.currentThread().getName());
//通过Thread.currentThread()调用当前的线程对象,获取到线程对象可以调用方法
}
}
1.4、线程的调度
常见的线程调度模型:
-
抢占式线程调度:哪个线程的优先级比较高,哪个线程抢占cpu时间片的概率就会高点。
java就是采用抢占式线程调度
-
均分式线程调度:平均分配cpu的时间片,每个线程占有的cpu的时间片是一样的
实例方法:
-
void setPriority(int newPriority)int getPriority()获取线程优先级最低优先级1
-
设置线程的优先级:
默认优先级是5
最高优先级10
优先级比较高的获取CPU时间片可能会多一些。
-
静态方法:
static void yield()
让位方法
暂停当前正在执行的线程对象,并执行其他线程
-
yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。
-
yield()方法的执行会让当前线程从“运行状态”回到"就绪状态”。
实例:
设置主副线程的优先级为5和7然后再主线程循环的时候,没十次进行一次让位,让出cpu,但是这个时候属于公平竞争,有可能还是由主线程继续抢夺到这个cpu,所以让位下一个时间片不一定会由副线程执行
package Thread;
public class Thread_Priority {
public static void main(String[] args) {
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
for (int i=0;i<99;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
});
thread.setPriority(5);
thread.start();
Thread.currentThread().setPriority(7);
for (int i=0;i<99;i++){
if (i%10==0){
System.out.println(1);
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
join方法
将副线程合并到主线程执行,变成一个线程,之上而下执行,会阻塞原有的线程
package Thread;
public class Thread_join {
public static void main(String[] args) {
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
for (int i=0;i<99;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
});
thread.setPriority(1);
thread.start();
try {
thread.join();//副线程和主线程合并,将副线程需要执行的内容放到这里,主线程阻塞
// 开辟的栈并没有合并到一起
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread.currentThread().setPriority(10);
for (int i=0;i<99;i++){
if (i%10==0){
System.out.println(1);
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
1.5、线程的安全问题
线程的安全问题的三个条件:
-
多线程并发
-
共享数据
-
共同修改共享数据
解决方法1:线程同步机制,将线程排队,通过牺牲一部分效率的方法来保证数据安全
同步和异步的概念:
-
同步就是排队,异步就是并发,各执行各的互不干扰
-
使用synchronized关键字来保证同步线程安全
实例:
创建一个银行账户类,有一个取钱方法,然后创建一个账户对象,开辟两个线程同时执行这个取钱方法,如果不加上synchronize关键字的话可能会出现数据异常的现象,(但是也不在一定出现)
在取钱方法中加一个一秒的睡眠可以保证,两个程序必然会出现数据错误的问题
然后 synchronize的括号中传递的必须是两个线程都有的对象,可以是当对象,也可以创建一个类中的对象。
可以将snchronized关键字卸载方法上,这样默认就使用this对象,范围是整个方法体
public int reduce(int money){
synchronized (this){
int after=this.balance-money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
return after;
}
}//取钱的方法
public class Thread_error {
public static void main(String[] args) {
Acoount acoount=new Acoount("aaa",10000);//创建账户对象
Thread thread1=new Thread(new myaccount(acoount));//将同一个账户传递进两个线程
Thread thread2=new Thread(new myaccount(acoount));
thread1.start();
thread2.start();//开启两个线程,在执行的时候交错执行。
}
}
class myaccount implements Runnable{
private Acoount act;
public myaccount(Acoount act){
this.act=act;
}
@Override
public void run() {
act.reduce(5000);
System.out.println(act.getName()+"取款成功"+"余额:"+act.getBalance());
}
}
线程的执行阶段:当共享对象锁的线程进入到锁池找共享对象的对象锁的时候就会释放掉占有的cpu,进入阻塞阶段。如果找到了就会进入就绪状态,如果找不到,就在锁池中等待。
java中三种变量那个不会有线程安全问题
-
实例变量:在堆中
-
静态变量:在方法区中
-
局部变量:在栈中,这个不会有线程安全问题(不共享)
1.6、synchronized有三种写法
-
同步代码块
synchronized(线程共享对象){}
线程共享的概念:是一种共享的引用对象,可以是类的this,可以是类中创建的其他对象,只要是共享的就行。也可以使用方法区的字符串常量池中的字符串引用。这样所有的线程都共享该方法。
可以是(this,obj,“abc”)
-
在实例方法上使用synchronized
表示共享对象一定是this
并且同步代码块是整个方法体。
-
在静态方法上使用synchronized
表示找类锁。
-
类锁永远只有1把。
-
就算创建了100个对象,那类锁也只有一把。
-
对象锁:1个对象1把锁,100个对象100把锁。
-
类锁:100个对象,也可能只是1把类锁。
理解:synchronized会使用对象锁,一个对象只有一把锁,如果一个对象有两个方法都是用了synchronized关键字,两个线程执行不同方法的时候会抢占同一把对象锁,必须等待。
同时synchronized关键字加在静态方法上会获取类锁,不会获取对象锁,类锁会让所有对象共享同一个锁,同时是静态方法才会排队类锁和对象锁不相同。
1.7、死锁
死锁的原理就是两个线程互相占用一个锁,然后继续获取 对方线程所占用的锁
public class dead_lock {
public static void main(String[] args) {
Object o1=new Object();
Object o2=new Object();
//创建两个对象
Thread t1=new mythread1(o1,o2);
Thread t2=new mythread2(o1,o2);
//将两个对象传递给两个线程
t1.start();
t2.start();
//开启线程
//开启线程后t1将获取到o1的对象锁,然后睡眠一秒等待o2被线程t2获取,
//然后t1线程开始等待获取o2的对象锁,t2线程开始等待o1的对象锁,这样就发生了死锁
}
}
class mythread1 extends Thread{
private Object obj1;
private Object obj2;
public mythread1(Object obj1,Object obj2){
this.obj1=obj1;
this.obj2=obj2;
}
@Override
public void run(){
synchronized (obj1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2){
}
}
}
}class mythread2 extends Thread{
private Object obj1;
private Object obj2;
public mythread2(Object obj1,Object obj2){
this.obj1=obj1;
this.obj2=obj2;
}
@Override
public void run(){
synchronized (obj2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1){
}
}
}
}
在开发中使用线程安全的方法:
-
首先使用局部变量代替成员变量(实例变量和静态变量)
-
如果必须是是实例变量,那么可以创建多个对象
-
如果都不行就只能使用synchronized关键字了,但是会降低效率。
守护线程
线程分为两大类:
-
一类是用户线程
-
一类是守护线程(后台线程)
守护线程的特点:一般是一个死循环,当所有的用户线程结束的时候,守护线程会自动结束
main方法就是一个用户线程
守护线程一般用来:
-
每天零点的时候系统备份
-
这个时候就会用到一个定时器,可以将定时器设置为守护线程。
-
t.setDaemon(true)守护线程
public class thread_protect {
public static void main(String[] args) {
Thread t=new Thread(new mythread());
t.setDaemon(true);//添加这个就可以变成守护线程,当所有主线程结束后会自动结束
t.start();
for (int i=0;i<100;i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"---------"+i);
}
}
}
class mythread extends Thread{
@Override
public void run(){
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--------->" );
}
}
}
定时器:使用方式
-
使用sleep方法,设置睡眠时间,比较low
-
使用java类库中定义的Timer类,可以直接用
-
可以使用spring框架中提供的springTask框架,配置使用
定时器的作用:间隔特定的时间执行特定的程序
这样每十秒就会运行一次
Timer对象的schedule方法有三个参数,第一个是运行方法,是一个抽象类,必须使用新建类来继承;第二个是时间,可以使用转变的确定时间,也可以直接使用当前时间。第三个参数是间隔(毫秒)
public class TimerTest {
public static void main(String[] args) throws ParseException {
Timer timer=new Timer();
// Timer timer1=new Timer(true);创建定时器为守护线程
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date=simpleDateFormat.parse("2022-09-15 22:09:00");//使用该方法将特定的时间赋值给date对象
timer.schedule(new LogTimerTask(),date,1000*10);
}
}
class LogTimerTask extends TimerTask{
@Override
public void run() {
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String strtime=simpleDateFormat.format(new Date());
System.out.println(strtime+"完成了一次运行");
}
}
实现线程的第三种方式:jdk8之后的:
实现callable接口,这种方式实现之后可以获取线程的返回值。
这种方式的优点:可以获取到线程的执行结果,
这种方式的缺点:主线程需要等待浮现副线程的运行结果,会阻塞,效率低
public class Thread_callable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建未来任务类
FutureTask task=new FutureTask(new Callable() {//匿名内部类的形式实现的callable接口,也可以单独实现
@Override
public Object call() throws Exception {//call方法相当于run方法,只不过有返回值
System.out.println("call method begin");
Thread.sleep(10000);
System.out.println("call method end");
int a=100;
int b=200;
return a+b;
}
});//相当于普通的Runnable接口,但是有返回值
Thread t=new Thread(task);
t.start();
Object o=task.get();//获取返回值数据,必须等待t线程执行结束,效率较低
System.out.println("main end");
}
}
关于Object类中的wait方法和notify方法
T线程在o对象上活动。T线程是当前线程对象。当调用o.wait)方法之后,T线程进入无期限等待。当前线程进入等待状态。直到最终调用o.notify0方法。
o.notify0方法的调用可以让正在o对象上等待的线程唤醒。
o.notifyall唤醒所有的等待线程
1.8、消费者生产者
实例,由于两个线程是部分并发的所以,代码执行相对复杂
在while循环里面加上synchronize锁会实现一较高的效率,如果将这个list扩容到10,里面的容量就会一直10,9,10,9这样(如果刚释放锁的那个线程抢的比较快的话会再次执行一遍,可能就会变成8)。但是如果这时候将synchronize放到循环的外面,就会在循环的时候一直持有锁,知道挂起线程,这样更好理解,但是效率会变低。
public class Thread_pro_con {
public static void main(String[] args) {
List list=new ArrayList();
Thread t1=new Thread(new product(list));
Thread t2=new Thread(new comsumer(list));
t1.start();
t2.start();
//创建两个线程运行
}
}
class product implements Runnable{
private List list;
public product(List list){
this.list=list;
}
@Override
public void run() {
while (true){
synchronized (list){//1.获取list对象锁
System.out.println(1);//这里会输出两次
if (list.size()>0){
try {
System.out.println(4);
list.wait();//4.到这里挂起该线程,释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {//2.生产一个商品,重新执行while
System.out.println(2);
list.add(1);
System.out.println("生产了一个"+list.size());
list.notify();//第二遍执行到这里的时候会将消费者挂起线程唤醒
}
}
}
}
}
class comsumer implements Runnable{
private List list;
public comsumer(List list){
this.list=list;
}
@Override
public void run() {
while (true){//3,t2线程在这里等待对象锁
System.out.println(3);
synchronized (list) {//5.t1释放对象锁之后,这里获取对象锁
System.out.println(5);
if (list.size() == 0) {
try {
System.out.println(7);
list.wait();//7.到这里挂起使用list的消费者
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
list.remove(0);//6.到这里执行
System.out.println(6);
System.out.println("消费了一个"+list.size());
list.notify();//第二遍执行到这里的时候,会将生产者挂起的线程唤醒
}
}
}
}
}
二、反射机制
2.1、反射机制有什么用
-
java中的反射机制可以操作字节码文件
-
可以读取和修改字节码文件
-
通过反射机制可以操作代码片段class文件
-
java.lang.Class:代表整个字节码,代表一个类型
-
java.lang.reflect.Method:代表字节码中的方法字节码。
-
java.lang.reflect.Constructor:代表字节码中的构造方法字节码。
-
java.lang.reflect.Field:代表字节码中的属性字节码
-
获取字节码的三种方式
public static void main(String[] args) {
//方法一,通过class类的静态方法forname获取,参数是一个完整的包名,可以是自定义的类,也可以是java自带的类
try {
Class a=Class.forName("Fanshe.account");
Class a1=Class.forName("java.lang.String");
System.out.println(a+"---------"+a1);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//第二种方式,使用对象来直接调用class方法就可以获取字节码,这种方法不能获取基本数据类型
String str="aaa";
Integer i=1;
Class B=str.getClass();
Class B1=i.getClass();
System.out.println(B+"---------"+B1);
//第三种方式,使用类名直接.class,只有这种方式可以获取基本数据类型的字节码
Class c=String.class;
Class c1=int.class;
Class c2=account.class;
System.out.println(c+"---------"+c1);
//获取字节码之后可以通过调用newInstance()方法来返回该字节码对应的对象
//该方法的返回值是object,所以需要向下转型才能返回
try {
account account= (Fanshe.account) c2.newInstance();
System.out.println(account);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
使用properties和class字节码的形式创建对象并输出
首先使用properties从文件中获取类的路径
然后通过获取字节码的forname方法来抓取并创建类对象
这样做的好处是可以通过修改文件中类的路径来打到创建不同类的方式
public class properties_fanse {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileReader fileReader=new FileReader("base_java/student");
Properties p=new Properties();
p.load(fileReader);
String path= (String) p.get("username");
Class c=Class.forName(path);
try {
account2 a= (account2) c.newInstance();
System.out.println(a);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
//文件内容username=Fanshe.account2
}
静态代码块在类加载的时候执行
使用class.forname(类)可以使该类加载,然后执行静态代码块
所以只希望执行类中静态代码块的话可以使用该方法。
2.2、读取文件路径方式
由于通过项目的相对路径获取绝对路径,所以可以在linux中也可以运行。不需要在idea里面。
//getContextClassLoader()可以获取到当前线程的类加载器对象
//getResource类加载器对象的方法,用于路径,一般在src下,然后返回绝对路径
public class properties_fanse {
public static void main(String[] args) throws IOException, ClassNotFoundException {
String path1=Thread.currentThread().getContextClassLoader().getResource("student.properties").getPath();
InputStream reader=Thread.currentThread().getContextClassLoader().getResourceAsStream("student.properties");
//这种方式可以直接用流返回,搭配properties的时候更加方便
System.out.println(path1);
FileReader fileReader=new FileReader(path1);//这样做的缺点是只能在idea里面运行
Properties p=new Properties();
p.load(fileReader);
String path= (String) p.get("username");
Class c=Class.forName(path);
try {
account2 a= (account2) c.newInstance();
System.out.println(a);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
获取配置文件的方法:
本质上其实就是文件流,但是是通过一个写好的类来直接获取,比较方便。
/*可以通过该方式来直接获取src文件下的配置文件
并且在获取的时候不能加上后缀*/
public class jdbc_file {
public static void main(String[] args) {
ResourceBundle resourceBundle=ResourceBundle.getBundle("jdbc");
String classname=resourceBundle.getString("username");
System.out.println(classname);
}
}
2.3、类加载器
分类:
-
启动类加载器
-
扩展类加载器
-
应用类加载器
代码在开始执行之前,会将所有需要的类加载到jvm中,然后通过类加载器。
jre是Java虚拟机的环境
-
首先通过“启动类加载器”加载.
注意:启动类加载器专门加载:C:\Program Files\Java\jdk1.8.\jre\lib\rt.jar
rt.jar中都是JDK最核心的类库。
-
如果通过启动类加载器”加载不到的时候,会通过“扩展类加载器”加载。
注意:扩展类加载器专门加载:C:\Program Files\Java\jdk1.8.0\jre\lib\ext.jar
-
如果"扩展类加载器”没有加载到,那么会通过应用类加载器”加载.
注意:应用类加载器专门加载:classpath中的类。
双亲委派机制:
优先从启动类加载器中加载,这个类称为父,然后从扩展类加载器中加载,称为母
如果都加载不到才会从应用加载器中加载。应用加载器存放的就是类路径下(一般是src)自己写的类中加载。这样就保证了安全。
2.4、反射机制实例
通过反射机制来捕捉类中的字段:
//通过捕捉字节码来输出类中的各种数据
/*
private int sno;
public String name;
protected String sex;
int age;
*/
public class Refiect {
public static void main(String[] args) throws ClassNotFoundException {
Class Stu=Class.forName("Fanshe.Student");
Field[] fields=Stu.getFields();//只能获取public修饰的字段
Field[] fields1=Stu.getDeclaredFields();//可以获取所有字段
for (Field field:fields){
System.out.println(field);//输出获取到的字段所有信息
System.out.println(field.getName());//输出字段的名字
Class stutype=field.getType();//首先获取字段类型的字节码
System.out.println(stutype.getSimpleName());//然后调用方法输出类型
System.out.println(field.getModifiers());//直接调用该方法可以直接输出修饰符权限,使用数字代替
System.out.println(Modifier.toString(field.getModifiers()));//通过该方法将数字转换为修饰符
}
for (Field field:fields1){
System.out.println(field);//输出获取到的字段所有信息
System.out.println(field.getName());//输出字段的名字
Class stutype=field.getType();//首先获取字段类型的字节码
System.out.println(stutype.getSimpleName());//然后调用方法输出类型
System.out.println(field.getModifiers());//直接调用该方法可以直接输出修饰符权限,使用数字代替
System.out.println(Modifier.toString(field.getModifiers()));//通过该方法将数字转换为修饰符
}
}
}
反编译FIeld
//获取类的字段信息
public class Refiect_fan {
public static void main(String[] args) throws ClassNotFoundException {
StringBuffer s=new StringBuffer();//字符串缓冲流,可以用来拼接字符串,不然就要使用String一直赋予,这样很浪费
Class stu=Class.forName("java.lang.StringBuffer");
s.append(Modifier.toString(stu.getModifiers())+ " class"+" "+stu.getSimpleName()+"(){");
Field[] fields=stu.getDeclaredFields();
for (Field field:fields){
s.append("\n");
s.append("\t");
s.append(Modifier.toString(stu.getModifiers())+" ");
s.append(field.getType().getSimpleName()+" ");
s.append(field.getName()+";");
}
s.append("\n"+"}");
System.out.println(s);
}
}
通过反射机制来访问对象属性
public class fanshe_field {
public static void main(String[] args) throws Exception {
Class c=Class.forName("Fanshe.Student");
Object student= c.newInstance();//用字节码创建对象
Field fieldSno=c.getDeclaredField("sno");//获取对象属性
fieldSno.setAccessible(true);//如果变量是私有的,则必须使用这个打破封装
Field fieldSex=c.getDeclaredField("sex");
fieldSex.set(student,"man");//赋值
fieldSno.set(student,586);
System.out.println(fieldSno.get(student)+"\n"+fieldSex.get(student));
}
}
可变参数:
//可变长度参数,就是类似于一个数组
//只能放到方法里面的最后一个位置,只有一个存在
//参数的个数事0~n个
//使用方法。类型...
public class change_number {
public static void main(String[] args) {
change_canshu.inta("dsa",1,2,3,4,5,3,31,1);
change_canshu.Stringa(515,"sd","a");
}
}
class change_canshu{
public static void inta(String a,int... b){
System.out.println(a);
for (int i:b){
System.out.println(i);
}
}
public static void Stringa(Integer a,String... b){
System.out.println(a);
for (String i:b){
System.out.println(i);
}
}
}
通过反射机制使用方法:
public class fanshe_methods {
public static void main(String[] args) throws Exception{
Class c=Class.forName("Fanshe.Student");
Object o=c.newInstance();//创建对象
Method methodLogin=c.getDeclaredMethod("login", String.class, String.class);//创建方法对象
Object objects=methodLogin.invoke(o,"admin","admin");//调用方法
System.out.println(objects);//输出true
Method[] methods=c.getDeclaredMethods();
for (Method method:methods){
System.out.println(method.getName());//方法名logout
System.out.println(method.getReturnType());//方法返回值类型void
System.out.println(Modifier.toString(method.getModifiers()));//方法修饰符public
Class[] list=method.getParameterTypes();//方法参数类型String,String
for (Class type:list){
System.out.println(type.getSimpleName());
}
}
}
}
反射机制使用构造方法:
public class fanshe_construct {
public static void main(String[] args) throws Exception{
Class c=Class.forName("Fanshe.Student");
Constructor con=c.getDeclaredConstructor(int.class,String.class,String.class,int.class);
Student student= (Student) con.newInstance(123,"张三","男",13);
System.out.println(student);
}
}
反射机制获取父类和接口
public class fanshe_fatherAndInterfaces {
public static void main(String[] args) throws Exception{
Class c=Class.forName("java.lang.String");
Class classSuper=c.getSuperclass();//获取父类
System.out.println(classSuper.getSimpleName());
Class[] classes=c.getInterfaces();//获取接口
for (Class class1:classes){
System.out.println(class1);
}
}
}
三、注解
3.1、基本解释
标示性注解:给编译器作参考的:
-
@Override
注解就是用来标识该方法是重写父类的方法 -
这个注解和运行阶段没有关系,并且只能标注方法
java.lang
包下的注释类型:
-
Deprecated
用@Deprecated
注释的程序元素不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。
也就是标识该元素是过时的,会加上一个横杠
-
@Override标识该方法是重写父类的方法
SuppressWarnings 指示应该在注释元素(以及包含在该注释元素中的 所有程序元素)中取消显示指定的编译器警告。 元注解 什么是元注解?
用来标注~注解类型”的“注解”,称为元注解。
常见的元注解有哪些?
-
Target:一般用于标识该注解是用在哪一个范围的,比如方法上,类上或者元素上
-
Retention:表示存在的时间,如果不设置,该注解在运行的时候就不存在
关于Target注解:
-
这是一个元注解,用来标注"注解类型”的"注解”
-
这个Target注解用来标注“被标注的注解”可以出现在哪些位置上。
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})//类,方法,字段
关于Retention注解:
-
这是一个元注解,用来标注“注解类型”的“注解”
-
这个Retention注解用来标注"被标注的注解”最终保存在哪里。
-
@Retention(RetentionPolicy.SOURCE):表示该注解只被保留在java源文件中。
-
@Retention(RetentionPolicy.CLASS):表示该注解被保存在class文件中。
-
@Retention(RetentionPolicy.RUNTIME):表示该注解被保存在class文件中,并在运行时存在
一般可以在注解中添加元素,如果是直接定义的元素必须在调用的时候赋值,如果已经使用default赋值过了就可以省略
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface myAnotation {
String name();
int age() default 22;
}
@myAnotation(name = "dsa",age = 11)//age可以不写
属性的类型可以是:
byte shart int Long float double boolean char String Class 教举类型
以及以上每一种的数组形式。