目录
1.多线程1
2.多线程2 + 注解
2.4.1 @Target ElementType.class
2.4.2 @Retention RetentionPolicy.class
3.反射 + 内部类
4.socket
1.多线程1
1.1 进程
1.1.1 概念
就是正在运行的程序。也就是代表了程序锁占用的内存区域
1.1.2 特点
-- 独立性:进程是系统中独立存在的实体,它可以拥有自己的独立的资源,每一个进程都拥有自己私有的地址空间。在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间
-- 动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。在进程中加入了时间的概念,进程具有自己的生命周期和各种不同的状态,这些概念在程序中都是不具备的
-- 并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响
1.2 线程
1.2.1 概念
线程(thread)是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位,一个进程可以开启多个线程
多线程扩展了多进程的概念,使得同一个进程可以同时并发处理多个任务
简而言之,一个程序运行后至少一个进程,一个进程里包含多个线程
如果一个进程只有一个线程,这种程序被称为单线程
如果一个进程中有多条执行路径被称为多线程程序
1.2.2 进程和线程的关系
从上图中可以看出一个操作系统中可以有多个进程,一个进程中可以有多个线程,每个进程有自己独立的内存,每个线程共享一个进程中的内存,每个线程又有自己独立的内存。(记清这个关系,非常重要!)
所以想使用线程技术,得先有进程,进程的创建是OS创建的,你能实现吗?不能,一般都是c或者c++语言完成的
1.3 多线程的特性
1.3.1 随机性
1.3.2 线程状态
线程生命周期,总共有五种状态:
1) 新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
2) 就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
3) 运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
4) 阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态;
4.1) 根据阻塞产生的原因不同,阻塞状态又可以分为三种:
a) 等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
b) 同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
c) 其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、 join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态;
6) 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
1.4 多线程创建:继承Thread
1.4.1 概述
Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例;
启动线程的唯一方法就是通过Thread类的start()实例方法,Start()方法是一个native方法,它将通知底层操作系统,最终由操作系统启动一个新线程,操作系统将执行run()方法;
这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法;
模拟开启多个线程,每个线程调用run()方法
1.4.2 常用方法
String getName()
返回该线程的名称
static Thread currentThread()
返回对当前正在执行的线程对象的引用
void setName(String name)
改变线程名称,使之与参数 name 相同
static void sleep(long millis)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响
void start()
使该线程开始执行;Java 虚拟机调用该线程的 run 方法
Thread(String name)
分配新的 Thread 对象
1.4.3 测试
package seday13new;
public class Test1 {
public static void main(String[] args) {
//3、创建线程对象
ThreadDemo t1 = new ThreadDemo("钢铁侠");
ThreadDemo t2 = new ThreadDemo("美队");
//4、开启线程:谁抢到资源谁就先执行
t1.start();
t2.start();
//t1.run(); //当做常规方法调用,且不会发生多线程现象
}
}
//1、作为Thread的子类,并重写run方法,把多线程的业务写在run方法中
class ThreadDemo extends Thread{
public ThreadDemo() {}
public ThreadDemo(String name) {
super(name);
}
@Override
public void run() {
//2、默认实现是super.run();
for (int i = 0; i < 10; i++) {
System.out.println(getName()+i);
}
}
}
执行结果:
hello0
hello1
hello2
hello3
hello4
hello5
hello6
hello7
hello8
hello1
hello2
hello3
hello9
hello4
hello5
hello6
hello7
hello8
hello9
注意:从上面结果可以确认,start()方法只是通知操作系统线程就绪,具体什么时间执行,操作系统来决定,我们JVM已经控制不了了
1.5 多线程创建:实现Runnable接口
1.5.1 概述
如果自己的类已经extends另一个类,就无法多继承,此时,可以实现一个Runnable接口
1.5.2 常用方法
void run()
使用实现接口 Runnable 的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的 run 方法
1.5.3 测试
package seday13new;
public class Test2 {
public static void main(String[] args) {
MyThread mt = new MyThread ();
//2,构造创建对象,传入Runnable子类
Thread target1 = new Thread(mt);
Thread target2 = new Thread(mt);
//开启线程
target1.start();
target2.start();
}
}
//1,实现Runnable接口,重写run()
class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
执行结果:
Thread-0
Thread-2
Thread-1
Thread-5
Thread-8
Thread-6
Thread-7
Thread-4
Thread-3
Thread-9
注意:可以看到执行顺序是乱的,我们已经知道start()方法只是通知操作系统线程就绪,具体什么时间执行,操作系统来决定,我们JVM已经控制不了了。这就是乱序的原因,也是正常的
1.5.4 比较
方式 | 优点 | 缺点 |
Thread | 编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程 | 线程类已经继承了Thread类,所以不能再继承其他父类 |
Runnable | 线程类只是实现了Runnable接口或Callable接口,还可以继承其他类 在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想 | 编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法 |
Callable | Runnable规定(重写)的方法是run() Callable规定(重写)的方法是call() Callable的任务执行后可返回值,而Runnable的任务是不能返回值的 Call方法可以抛出异常,run方法不可以 运行Callable任务可以拿到一个Future对象,表示异步计算的结果 | 存取其他项慢 |
Pool | 线程池可以创建固定大小 这样无需反复创建线程对象,线程是比较耗费资源的资源 同时线程不会一直无界的创建下去,拖慢系统 | 编程繁琐,难以理解 |
1.6 售票案例
设计4个售票窗口,总计售票100张,用多线程的程序设计并写出代码
1.6.1 方案1:继承Thread
package seday13new;
public class Test3 {
public static void main(String[] args) {
Ticket t1 = new Ticket();
Ticket t2 = new Ticket();
Ticket t3 = new Ticket();
Ticket t4 = new Ticket();
//问题:票好像卖重复了,同一张票卖了好多次...
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Ticket extends Thread {
//卖了200张票,变成static的
static private int tic = 100;
@Override
public void run() {
while (true) {
//tic=1时,谁都可以进来,t t2 t3 t4
if (tic > 0) {
try {
//t t2 t3 t4都睡了
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//t醒了,tic--=1,tic=0;
//t2醒了,tic--=0,tic=-1;
//t3醒了,tic--=-1,tic=-2;
//t4醒了,tic--=-2,tic=-3;
System.out.println(tic--);
}
}
}
}
1.6.2 方案2:实现Runnable
package seday13new;
public class Test4 {
public static void main(String[] args) {
//只创建一次,就100张票
Ticket2 t = new Ticket2();
Thread target1 = new Thread(t,"窗口1");
Thread target2 = new Thread(t,"窗口2");
Thread target3 = new Thread(t,"窗口3");
Thread target4 = new Thread(t,"窗口4");
target1.start();
target2.start();
target3.start();
target4.start();
}
}
class Ticket2 implements Runnable{
private int tickets=100;
@Override
public void run() {
while(true) {
if(tickets >0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+tic--);
}
}
}
}
1.6.3 问题
1、 每次创建线程对象,都会生成一个tickets变量值是100,创建4次对象就生成了400张票了。不符合需求,怎么解决呢?能不能把tickets变量在每个对象间共享,就保证多少个对象都是卖这100张票。-- 用静态修饰
2、 产生超卖,-1张、-2张;
3、 产生重卖,同一张票卖给多人;
4、 多线程安全问题是如何出现的?常见情况是由于线程的随机性+访问延迟;
5、 以后如何判断程序有没有线程安全问题?在多线程程序中+有共享数据+多条语句操作共享数据;
2.多线程2 + 注解
2.1 同步锁
把有可能出现问题的代码包起来,一次只让一个线程执行
通过sychronized关键字实现同步,当多个对象操作共享数据时,可以使用同步锁解决线程安全问题
2.1.1 synchronized
synchronized(对象){
需要同步的代码;
}
2.1.1 特点
1、 前提1,同步需要两个或者两个以上的线程;
2、 前提2,多个线程间必须使用同一个锁。
3、 同步的缺点是会降低程序的执行效率, 为了保证线程安全,必须牺牲性能。
4、 可以修饰方法称为同步方法,使用的锁对象是this。
5、 可以修饰代码块称为同步代码块,锁对象可以任意。
2.1.2 改造
package seday13new;
public class Test4 {
public static void main(String[] args) {
Ticket2 t = new Ticket2();
Thread target1 = new Thread(t, "窗口1");
Thread target2 = new Thread(t, "窗口2");
Thread target3 = new Thread(t, "窗口3");
Thread target4 = new Thread(t, "窗口4");
target1.start();
target2.start();
target3.start();
target4.start();
}
}
class Ticket2 implements Runnable {
private int tic = 100;
Object obj = new Object();
@Override
public void run() {
while (true) {
// 把有线程安全问题的代码,用同步关键字包起来
// 原理:用一个对象作为一把锁,给代码上锁,一个线程访问锁代码时,其他线程只能等待锁释放才能进来
// 多线程间要使用同一把锁才可以真的把代码锁住实现线程安全
// synchronized (new Object()) { //锁了不同对象
// synchronized (obj) { //锁了同一个对象
//synchronized (Ticket2.class) { //锁了本类,针对于静态
synchronized (this) {
if (tic > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(tic--);
}
}
}
}
}
2.2 单例设计模式
2.2.1 概念
单例模式可以说是大多数开发人员在实际中使用最多的,常见的Spring默认创建的bean就是单例模式的;
单例模式有很多好处,比如可节约系统内存空间,控制资源的使用;
其中单例模式最重要的是确保对象只有一个;
简单来说,保证一个类在内存中的对象就一个;
RunTime就是典型的单例设计,我们通过对RunTime类的分析,一窥究竟;
2.2.2 源码剖析
/**
* Every Java application has a single instance of class
* <code>Runtime</code> that allows the application to interface with
* the environment in which the application is running. The current
* runtime can be obtained from the <code>getRuntime</code> method.
* <p>
* An application cannot create its own instance of this class.
*
* @author unascribed
* @see java.lang.Runtime#getRuntime()
* @since JDK1.0
*/
RunTime.java
package java.lang;
public class Runtime {
//1、创建静态的全局唯一的对象
private static Runtime currentRuntime = new Runtime();
//2、私有构造方法,不让外部来调用
/** Don't let anyone else instantiate this class */
private Runtime() {}
//3、通过自定义的静态方法获取实例
public static Runtime getRuntime() {
return currentRuntime;
}
}
2.2.3 饿汉式
package cn.tedu.design;
//测试单例设计模式--就是按照一定的开发步骤,按照一定的模板进行开发,达到程序中只会有一个实例在干活的目的!!
public class Test5_Design {
public static void main(String[] args) {
//4,测试 new多少次都是一个对象???--
//MySingleTon m = new MySingleTon(); --构造方法私有化
MySingleTon m1 = MySingleTon.getMy();
MySingleTon m2 = MySingleTon.getMy();
System.out.println(m1==m2); //是同一个对象吗???
System.out.println(m1.equals(m2)); //默认用Object的==
}
}
class MySingleTon {
//1,私有化改造方法 -- 目的就是控制外界创建对象的权利
private MySingleTon() {}
//2,封装创建好的对象 -- 封装,不让外界随意看到我创建的对象
static private MySingleTon my = new MySingleTon();
//3,提供公共的获取对象的方法
//静态方法,因为没法new了,还想用,就用类名访问--修饰成静态的
static public MySingleTon getMy(){
return my; //静态只能调静态
}
}
2.2.4 懒汉式
//懒汉式 -- 面试重点!!延迟加载思想+线程不安全
class MySingleTon2 {
// 1,私有化改造方法 -- 目的就是控制外界创建对象的权利
private MySingleTon2() {}
// 2,封装创建好的对象 -- 先不创建对象,啥时候用啥时候创建!!
static private MySingleTon2 my;
// 3,提供公共的获取对象的方法
// 静态方法,因为没法new了,还想用,就用类名访问--修饰成静态的
synchronized static public MySingleTon2 getMy() { //b,方法里都是同步代码,可以是同步方法
// synchronized (MySingleTon2.class) { //a,同步代码块,静态方法的锁,是类名.class本类的字节码对象
// t1,t2,t3来准备new
if (my == null) {
// t1 new
// t2 new
// t3 new
my = new MySingleTon2(); // 开始创建!! 延迟加载
}
// }
return my; // 静态只能调静态
}
}
2.3 注解
2.3.1 概念
注解很厉害,它可以增强我们的java代码,同时利用反射技术可以扩充实现很多功能,它们被广泛应用于三大框架底层,传统我们通过xml文本文件声明方式,而现在最主流的开发都是基于注解方式,代码量少,框架可以根据注解去自动生成很多代码,从而减少代码量,程序更易读。例如最火爆的SpringBoot就完全基于注解技术实现;
注解设计非常精巧,初学时觉得很另类甚至多余,甚至垃圾。有了java代码干嘛还要有@注解呢?但熟练之后你会赞叹,它竟然可以超越java代码的功能,让java代码瞬间变得强大。大家慢慢体会吧;
常见的元注解:@Target、@Retention,jdk提供将来描述我们自定义的注解的注解。听起来好绕,别着急,做两个例子,立刻清晰。现在现有“元注解”这个概念;
2.3.2 分类
-- JDK自带注解
-- 元注解
-- 自定义注解
2.3.3 JDK注解
JDK注解的注解,就5个:
-- @Override
-- @Deprecated 标记就表明这个方法已经过时了,但我就要用,别提示我过期
-- @SuppressWarnings(“deprecation”) 忽略警告
-- @SafeVarargs jdk1.7出现,堆污染,不常用
-- @FunctionallInterface jdk1.8出现,配合函数式编程拉姆达表达式,不常用
2.3.4 元注解
描述注解的注解,就5个:
-- @Target 注解用在哪里:类上、方法上、属性上
-- @Retention 注解的生命周期:源文件中、class文件中、运行中
-- @Inherited 允许子注解继承
-- @Documented 生成javadoc时会包含注解,不常用
-- @Repeatable注解为可重复类型注解,可以在同一个地方多次使用,不常用
2.4 元注解
2.4.1 @Target ElementType.class
描述注解的使用范围:
l ElementType.ANNOTATION_TYPE 应用于注释类型
l ElementType.CONSTRUCTOR 应用于构造函数
l ElementType.FIELD 应用于字段或属性
l ElementType.LOCAL_VARIABLE 应用于局部变量
l ElementType.METHOD 应用于方法级
l ElementType.PACKAGE 应用于包声明
l ElementType.PARAMETER 应用于方法的参数
l ElementType.TYPE 应用于类的元素
2.4.2 @Retention RetentionPolicy.class
定义了该注解被保留的时间长短,某些注解仅出现在源代码中,而被编译器丢弃;
而另一些却被编译在class文件中; 编译在class文件中的注解可能会被虚拟机忽略,而另一些在class被装载时将被读取;
为何要分有没有呢?没有时,反射就拿不到,从而就无法去识别处理;
l SOURCE 在源文件中有效(即源文件保留)
l CLASS 在class文件中有效(即class保留)
l RUNTIME 在运行时有效(即运行时保留)
2.5 自定义注解
2.5.1 定义注解
//1,定义注解
//1.1,设置注解的使用范围@Target,啥都不写,哪儿都能用
//@Target({ElementType.METHOD}) //作用于方法上
//@Target({ElementType.FIELD}) //作用于属性上
@Target({ElementType.METHOD , ElementType.PACKAGE}) //作用范围
@Retention(RetentionPolicy.SOURCE) //生命周期
@Target({ElementType.TYPE}) //作用于类上
@interface Test{
//3,定义属性
int age() default 0; //使用时,必须给age属性赋值,如:age=X。除非设置好默认值
//()不是参数,也不能写参数,只是特殊语法
//4,特殊属性value
String value() default ""; //使用时,必须给value属性赋值,如:X | value=X。除非设置好默认值
}
注意:注解的语法写法和常规java的语法写法不同
2.5.2 使用注解
//2,使用注解
//@Test
//5,注解的组合属性
@Test(value="",age=0)
class HelloTest{
// @Test(value="",age=0)
String name;
}
2.5.3 解析注解
判断注解是否存在
package javapro.spring.annotation;
public class TestAnnotation {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> clazz = Class.forName("javapro.spring.annotation.HelloController");
Controller c = clazz.getAnnotation(Controller.class);
if( c != null) {
System.out.println(c);
System.out.println(c.value());
}
}
}
3.反射 + 内部类
3.1 反射
3.1.1 概念
Reflection(反射) 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,也有称作“自省”。反射非常强大,它甚至能直接操作程序的私有属性。我们前面学习都有一个概念,private的只能类内部访问,外部是不行的,但这个规定被反射赤裸裸的打破了;
反射就像一面镜子,它可以在运行时获取一个类的所有信息,可以获取到任何定义的信息(包括成员变量,成员方法,构造器等),并且可以操纵类的字段、方法、构造器等部分;
3.1.2 为什么需要反射
好好的我们new User(); 不是很好,为什么要去通过反射创建对象呢?
那我要问你个问题了,你为什么要去餐馆吃饭呢?
例如:我们要吃个牛排大餐,如果我们自己创建,就什么都得管理
好处是,每一步做什么我都很清晰,坏处是什么都得自己实现,那不是累死了。牛接生你管,吃什么你管,屠宰你管,运输你管,冷藏你管,烹饪你管,上桌你管。就拿做菜来说,你能有特级厨师做的好?
那怎么办呢?有句话说的好,专业的事情交给专业的人做,饲养交给农场主,屠宰交给刽子手,烹饪交给特级厨师。那我们干嘛呢?
我们翘起二郎腿直接拿过来吃就好了
再者,饭店把东西做好,不能扔到地上,我们去捡着吃吧,那不是都成原始人了。那怎么办呢?很简单,把做好的东西放在一个容器中吧,如把牛排放在盘子里
在开发的世界里,spring就是专业的组织,它来帮我们创建对象,管理对象。我们不在new对象,而直接从spring提供的容器中beans获取即可。Beans底层其实就是一个Map<String,Object>,最终通过getBean(“user”)来获取。而这其中最核心的实现就是利用反射技术
总结一句,类不是你创建的,是你同事或者直接是第三方公司,此时你要或得这个类的底层功能调用,就需要反射技术实现。有点抽象,别着急,我们做个案例,你就立马清晰
3.1.3 反射Class类对象
Class.forName(“类的全路径”);
类名.class
对象.getClass();
3.1.4 常用方法
获得包名、类名
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)
反射新建实例
c.newInstance(); //执行无参构造
c.newInstance(6, "abc"); //执行有参构造
c.getConstructor(int.class, String.class); //执行含参构造,获取构造方法
反射调用成员变量
c.getDeclaredField(变量名); //获取变量
c.setAccessible(true); //使私有成员允许访问
f.set(实例, 值); //为指定实例的变量赋值,静态变量,第一参数给 null
f.get(实例); //访问指定实例的变量的值,静态变量,第一参数给 null
反射调用成员方法
获取方法
Method m = c.getDeclaredMethod(方法名, 参数类型列表);
m.setAccessible(true) ; //使私有方法允许被调用
m.invoke(实例, 参数数据) ; //让指定的实例来执行该方法
3.2 反射的应用
3.2.1 获取类对象
private static void method() throws Exception {
Class clazz = Student.class;
Class<?> clazz2 = Class.forName("seday15.Student");
Class clazz3 = new Student().getClass();
System.out.println(clazz.getName());
System.out.println(clazz2.getName());
System.out.println(clazz3.getName());
}
3.2.2 获取构造方法
private static void method3(Class clazz) {
Constructor[] cs = clazz.getDeclaredConstructors();
for (Constructor c : cs) {
String name = clazz.getSimpleName();
System.out.println(name);
Class[] cs2 = c.getParameterTypes(); //参数
System.out.println(Arrays.toString(cs2));
}
}
3.2.3 获取成员方法
private static void method4(Class clazz) {
Method[] ms = clazz.getMethods();
for (Method m : ms) {
String name = m.getName();
System.out.println(name);
Class<?>[] cs = m.getParameterTypes();
System.out.println(Arrays.toString(cs));
}
}
3.2.4 获取成员变量
private static void method2(Class clazz) {
Field[] fs = clazz.getFields(); //获取public的属性
for (Field f : fs) {
String name = f.getName();
String tname = f.getType().getSimpleName();
System.out.println(name);
System.out.println(tname);
}
}
3.2.5 创建对象
package seday15;
import java.lang.reflect.Constructor;
import java.util.Scanner;
//反射新建两个对象
public class Test3 {
public static void main(String[] args) throws Exception {
String s = new Scanner(System.in).nextLine();
Class<?> clazz = Class.forName(s);
Object o1 = clazz.newInstance(); //用无参构造
System.out.println(o1);
Constructor<?> c = clazz.getConstructor(String.class); //用含参构造
Object o2 = c.newInstance("jack");
System.out.println(o2);
}
}
3.3 暴力反射
指可以将程序中的私有的属性或者方法通过反射技术,暴力的获取到资源。需要使用的常见方法如下:
3.3.1 创建Person类
class Person{
private String name="jack";
private int age = 30;
private void show(int[] a) {
System.out.println("show()..."+Arrays.toString(a));
}
private void test() {
System.out.println("test()...");
}
}
3.3.2 测试
1、 获取私有属性值并修改
2、 获取私有方法并执行
package seday16new;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test3_ReflectPerson {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("seday16new.Person");
// method(clazz); //隐私属性
method2(clazz); //执行方法
}
private static void method2(Class<?> clazz) throws Exception {
Method m = clazz.getDeclaredMethod("show", int[].class);
Object obj = clazz.newInstance();
m.setAccessible(true); //方法隐私可见
m.invoke(obj, new int[]{1,2,3}); //执行
}
private static void method(Class clazz) throws Exception {
Field f = clazz.getDeclaredField("name");
System.out.println(f.getType().getName());
f.setAccessible(true); //属性隐私可见
Object obj = clazz.newInstance();
// f.set(obj, "rose"); //设置值
System.out.println(f.get(obj)); //获取值
//---所有属性
Field[] fs = clazz.getDeclaredFields();
for (Field ff : fs) {
System.out.println(ff);
ff.setAccessible(true); //暴力反射
System.out.println(ff.get(obj));
}
}
}
3.4 内部类
3.4.1 概述
如果一个类存在的意义就是为指定的另一个类,可以把这个类放入另一个类的内部,就是把类定义在类的内部的情况就可以形成内部类的形式;
A类中又定义了B类,B类就是内部类,B类可以当做A类的一个成员看待;
3.4.2 特点
1、 内部类可以直接访问外部类中的成员,包括私有成员
2、 外部类要访问内部类的成员,必须要建立内部类的对象
3、 在成员位置的内部类是成员内部类
4、 在局部位置的内部类是局部内部类
3.4.3 成员内部类
被private修饰
package cn.tedu.inner;
//测试内部类被private修饰
public class Test5_InnerClass2 {
public static void main(String[] args) {
//TODO 创建内部类对象,并执行show()
// Outer2.Inner2 oi = new Outer2().new Inner2(); //报错,Inner2已经被private了
//3,测试被private的内部类的资源能否执行!
new Outer2().test();
}
}
class Outer2{
//2,如果想要访问private的内部类,可以访问外部类提供的对应方法
public void test() {
//访问内部类方法
new Inner2().show();
}
//位置在类里方法外--成员内部类
//1,内部类可以被private修饰,但是外界无法直接创建对象了!
private class Inner2{
public void show() {
System.out.println("Inner2.show()");
}
}
}
被static修饰
package cn.tedu.inner;
//测试内部类被static修饰
public class Test6_InnerClass3 {
public static void main(String[] args) {
// 创建内部类对象测试show()
// Outer3.Inner3 oi = new Outer3().new Inner3(); //报错,原因是Inner3是静态的内部类
Outer3.Inner3 oi = new Outer3.Inner3(); //Outer3.Inner3通过类名.调用类中的静态资源 oi.show();
Outer3.Inner3.show2(); //调用静态内部类里的静态方法
}
}
class Outer3{
//1,内部类被static修饰--随着类的加载而加载,会造成内存资源浪费,并不常用!
static class Inner3{
public void show() {
System.out.println("Inner3.show()");
}
static public void show2() {
System.out.println("Inner3.show2()");
}
}
}
3.4.4 匿名内部类
匿名内部类属于局部内部类,并且是没有名字的内部类;
package cn.tedu.inner;
//测试匿名内部类
public class Test8_InnerClass5 {
public static void main(String[] args) {
new Hello() { // 匿名对象,本身接口不能new,这里new Hello()匿名对象,就相当于Hello接口的实现类
// 匿名内部类
@Override
public void save() {
System.out.println("save()..");
}
@Override
public void update() {
System.out.println("update()..");
}
}.update(); // 触发指定的方法
new Hello2() { //抽象类的匿名内部类
@Override
public void show() {}
}.show();
new Animal() { //普通类的匿名内部类
@Override
public void eat() {}
};
}
}
//TODO 创建匿名对象+匿名内部类测试
class Animal{
public void eat() {}
}
abstract class Hello2 {
abstract public void show();
public void delete() {}
}
// 定义接口
interface Hello {
void save();
void update();
}
4.socket
4.1 网络
查看本机ip地址
4.2 Socket
4.2.1 概述
也叫套接字编程,是一个抽象层;
应用程序可以通过它发送或接收数据,可对其像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口与协议的组合;
Socket就是为网络编程提供的一种机制 / 通信的两端都有Socket;
网络通信其实就是Socket间的通信 / 数据在两个Socket间通过IO传输;
4.3 服务器端-ServerSocket
在服务器端,选择一个端口号,在指定端口上等待客户端发起连接;
启动服务:ServerSocket ss = new ServerSocket(端口);
等待客户端发起连接,并建立连接通道:Sokcet socket = ss.accept();
4.4 客户端-Socket
新建Socket对象,连接指定ip的服务器的指定端口
Socket s = new Socket(ip, port);
从Socket获取双向的流
InputStream in = s.getInputStream();
OutputStream out = s.getOutputStream();
4.5 入门案例
服务器端接收客户端发来的hello,并给客户端响应hello
4.5.1 服务器端
说明其中,server端的accept()是阻塞的,客户端不连接,服务器不执行后面流程;
in.read()也是阻塞的,读不到就死等;
package seday16;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws Exception {
//1,在指定端口启动服务
ServerSocket server = new ServerSocket(8000);
//2,等待客户端发起连接,并建立通道
Socket socket = server.accept();
//3,取出双向的流
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
//4,通信
/*
* 通信协议:
* 1,通信流程
* 2,数据格式
* 先接收hello,在发送world
*/
//接收客户端发来的hello
for(int i = 0 ; i < 5 ;i++) {
//一个一个字节从网络流中读取客户端发来的数据
char c = (char) in.read();
System.out.print(c); //一行展示收到的数据
}
//给客户端发送数据
out.write("world".getBytes());
out.flush(); //刷出内存缓存
//释放资源
socket.close(); //断开连接
server.close(); //释放端口
}
}
4.5.2 客户端
package seday16;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws Exception, IOException {
//1,与服务器建立连接
//同桌启动服务器,你当客户端发送
Socket s = new Socket("127.0.0.1",8000);
//2,取出双向的流
InputStream in = s.getInputStream();
OutputStream out = s.getOutputStream();
//3,通信
/*
* 先发送Hello,再接收world
*/
//给服务器发送数据
out.write("hello".getBytes());
out.flush();
//接收服务器响应的数据
for (int i = 0; i < 5 ; i++) {
char c = (char) in.read();
System.out.print(c); //同一行展示
}
//释放资源
s.close();
}
}