黑马视频 (7/7)
Java SE
day25 线程 && GUI
- 单例设计模式:保证类在内存中只有一个对象。
如何保证类在内存中只有一个对象呢?
- 控制类的创建,不让其他类来创建本类的对象。private
- 在本类中定义一个本类的对象。Singleton s ;
- 提供公共的访问方式。 public static Singleton getInstance(){return s}
饿汉式 :开发用这种方式。
//饿汉式 class Singleton { //1,私有构造函数 private Singleton(){} //2,创建本类对象 private static Singleton s = new Singleton(); //3,对外提供公共的访问方法 public static Singleton getInstance() { return s; } public static void print() { System.out.println("11111111111"); } }
懒汉式 面试写这种方式。多线程的问题?
// 懒汉式 , 单例的延迟加载模式 class Singleton { //1,私有构造函数 private Singleton(){} //2,声明一个本类的引用 private static Singleton s; //3,对外提供公共的访问方法 public static Singleton getInstance() { if(s == null) //线程1,线程2 s = new Singleton(); return s; } public static void print() { System.out.println("11111111111"); } }
区别:
- 饿汉式是空间换时间,懒汉式是时间换空间
- 在多线程访问时候,饿汉式不会创建多个对象,但是懒汉式可能会创建多个对象。
第三种格式`
class Singleton { private Singleton() {} public static final Singleton s = new Singleton();//final是最终的意思,被final修饰的变量不可以被更改 }
Runtime类是一个单例类
Runtime r = Runtime.getRuntime(); //r.exec("shutdown -s -t 300"); //300秒后关机 r.exec("shutdown -a"); //取消关机
Timer类:计时器
public class Demo5_Timer { public static void main(String[] args) throws InterruptedException { Timer t = new Timer(); //指定时间安排指定任务,第一个参数是任务,第二个是时间,第三个是过多久之后再重复执行 t.schedule(new MyTimerTask(), new Date(114,9,15,10,54,20),3000); while(true) { System.out.println(new Date()); Thread.sleep(1000); } } } class MyTimerTask extends TimerTask { @Override public void run() { System.out.println("起床背英语单词"); } }
什么时候需要通信
- 多个线程并发执行时, 在默认情况下 CPU 是随机切换线程的
- 如果我们希望他们有规律的执行, 就可以使用通信, 例如每个线程执行一次打印
怎么通信
- 如果希望线程等待, 就调用wait()
- 如果希望唤醒等待的线程, 就调用notify();
- 这两个方法必须在同步代码中执行, 并且使用同步锁对象来调用
多个线程通信的问题
- notify( ) 方法是随机唤醒一个线程
- notifyAll() 方法是唤醒所有线程
- JDK5 之前无法唤醒指定的一个线程
- 如果多个线程之间通信, 需要使用 notifyAll() 通知所有线程, 用 while 来反复判断条件
- 在同步代码块中,使用哪个对象锁,就用哪个对象调用 wait 方法。因为对象锁可以使任意对象, Object 是所有类的基类,所以 wait 和 notify 方法 定义在 Object 这个类中。
sleep 和 wait 方法的区别:
- sleep 方法必须传入参数,参数就是时间,时间到了可以自动醒来
- wait 方法可以传入参数也可以不传入参数,传入参数就是在参数的时间结束后等待,不传入参数就是直接等待,
- sleep 方法在同步函数或者同步代码块中,不释放锁,睡着了也抱着锁睡。
- wait 方法在同步函数或者同步代码块中,释放锁
- 使用 ReentrantLock 类的 lock() (获取锁)和 unlock()(释放锁) 方法进行同步
通信
- 使用 ReentrantLock 类 的 newCondition() 方法可以获取 Condition 对象
- 需要等待的时候使用 Condition的await() 方法, 唤醒的时候用 signal() 方法
- 不同的线程使用不同的 Condition , 这样就能区分唤醒的时候找哪个线程了
- Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
默认情况下,所有的线程都属于主线程组。
* public final ThreadGroup getThreadGroup()//通过线程对象获取他所属于的组 * public final String getName()//通过线程组对象获取他组的名字
我们也可以给线程设置分组
ThreadGroup(String name) // 创建线程组对象并给其赋值名字 //创建线程对象 Thread(ThreadGroup?group, Runnable?target, String?name) // 设置整组的优先级或者守护线程
线程组的使用,默认是主线程组
MyRunnable mr = new MyRunnable(); Thread t1 = new Thread(mr, "张三"); Thread t2 = new Thread(mr, "李四"); //获取线程组 // 线程类里面的方法:public final ThreadGroup getThreadGroup() ThreadGroup tg1 = t1.getThreadGroup(); ThreadGroup tg2 = t2.getThreadGroup(); // 线程组里面的方法:public final String getName() String name1 = tg1.getName(); String name2 = tg2.getName(); System.out.println(name1); System.out.println(name2); // 通过结果我们知道了:线程默认情况下属于main线程组 // 通过下面的测试,你应该能够看到,默任情况下,所有的线程都属于同一个组 System.out.println(Thread.currentThread().getThreadGroup().getName());
自己设定线程组
// ThreadGroup(String name) ThreadGroup tg = new ThreadGroup("这是一个新的组") ; MyRunnable mr = new MyRunnable(); // Thread(ThreadGroup group, Runnable target, String name) Thread t1 = new Thread(tg, mr, "张三"); Thread t2 = new Thread(tg, mr, "李四"); System.out.println(t1.getThreadGroup().getName()); System.out.println(t2.getThreadGroup().getName()); //通过组名称设置后台线程,表示该组的线程都是后台线程 tg.setDaemon(true);
- 线程的 5 种状态 : 新建,就绪,运行,阻塞,死亡
- 线程池概述: 程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池 。
Executors工厂类来产生线程池,有如下几个方法
* public static ExecutorService newFixedThreadPool(int nThreads) * public static ExecutorService newSingleThreadExecutor()
使用步骤:
- 创建线程池对象
- 创建Runnable实例
- 提交Runnable实例
- 关闭线程池
这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行 Runnable 对象或者 Callable 对象代表的线程。它提供了如下方法
* Future<?> submit(Runnable task) * <T> Future<T> submit(Callable<T> task)
提交的是Callable
// 创建线程池对象 ExecutorService pool = Executors.newFixedThreadPool(2); // 可以执行Runnable对象或者Callable对象代表的线程 Future<Integer> f1 = pool.submit(new MyCallable(100)); Future<Integer> f2 = pool.submit(new MyCallable(200)); // V get() Integer i1 = f1.get(); Integer i2 = f2.get(); System.out.println(i1); System.out.println(i2); // 结束 pool.shutdown(); public class MyCallable implements Callable<Integer> { private int number; public MyCallable(int number) { this.number = number; } @Override public Integer call() throws Exception { int sum = 0; for (int x = 1; x <= number; x++) { sum += x; } return sum; } }
- 简单工厂模式概述: 又叫静态工厂方法模式,它定义一个具体的工厂类负责创建一些类的实例
- 客户端不需要在负责对象的创建,从而明确了各个类的职责
- 缺点:这个静态工厂类负责所有对象的创建,如果有新的对象增加,或者某些对象的创建方式不同,就需要不断的修改工厂类,不利于后期的维护
案例演示
- 动物抽象类:public abstract Animal { public abstract void eat(); }
- 具体狗类:public class Dog extends Animal {}
- 具体猫类:public class Cat extends Animal {}
- 开始,在测试类中每个具体的内容自己创建对象,但是,创建对象的工作如果比较麻烦,就需要有人专门做这个事情,所以就知道了一个专门的类来创建对象。
public class AnimalFactory { private AnimalFactory(){} ; //public static Dog createDog() {return new Dog();} //public static Cat createCat() {return new Cat();} //改进 public static Animal createAnimal(String animalName) { if(“dog”.equals(animalName)) {} else if(“cat”.equals(animale)) { }else { return null; } } }
- 工厂方法模式: 工厂方法模式中抽象工厂类负责定义创建对象的接口,具体对象的创建工作由继承抽象工厂的具体类实现。
优点
- 客户端不需要在负责对象的创建,从而明确了各个类的职责,如果有新的对象增加,只需要增加一个具体的类和具体的工厂类即可,不影响已有的代码,后期维护容易,增强了系统的扩展性
缺点
- 需要额外的编写代码,增加了工作量。
- .13_GUI(如何创建一个窗口并显示)
- Graphical User Interface(图形用户接口)。
Frame f = new Frame(“my window”); f.setLayout(new FlowLayout());//设置布局管理器 f.setSize(500,400);//设置窗体大小 f.setLocation(300,200);//设置窗体出现在屏幕的位置 f.setIconImage(Toolkit.getDefaultToolkit().createImage("qq.png")); f.setVisible(true);
- GUI ( 布局管理器 )
FlowLayout(流式布局管理器)
- 从左到右的顺序排列。
- Panel 默认的布局管理器。
BorderLayout(边界布局管理器)
- 东,南,西,北,中
- Frame默认的布局管理器。
GridLayout(网格布局管理器)
- 规则的矩阵
CardLayout(卡片布局管理器)
- 选项卡
GridBagLayout(网格包布局管理器)
- 非规则的矩阵
GUI(窗体监听
Frame f = new Frame("我的窗体"); //事件源是窗体,把监听器注册到事件源上 //事件对象传递给监听器 f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { //退出虚拟机,关闭窗口 System.exit(0); } });
- 鼠标监听
- 键盘监听和键盘事件
- 动作监听
设计模式 适配器设计模式 什么是适配器
- 在使用监听器的时候, 需要定义一个类事件监听器接口.
- 通常接口中有多个方法, 而程序中不一定所有的都用到, 但又必须重写, 这很繁琐.
- 适配器简化了这些操作, 我们定义监听器时只要继承适配器, 然后重写需要的方法即可.
适配器原理
- 适配器就是一个类, 实现了监听器接口, 所有抽象方法都重写了, 但是方法全是空的.
- 适配器类需要定义成抽象的,因为创建该类对象,调用空方法是没有意义的
- 目的就是为了简化程序员的操作, 定义监听器时继承适配器, 只重写需要的方法就可以了.
- 需要知道的
事件处理
- 事件: 用户的一个操作
- 事件源: 被操作的组件
- 监听器: 一个自定义类的对象, 实现了监听器接口, 包含事件处理方法,把监听器添加在事件源上, 当事件发生的时候虚拟机就会自动调用监听器中的事件处理方法
day26 网络编程
- 计算机网络: 是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。
- 网络编程: 就是用来实现网络互连的不同计算机上运行的程序间可以进行数据交换。
IP
- 每个设备在网络中的唯一标识
- 每台网络终端在网络中都有一个独立的地址,我们在网络中传输数据就是使用这个地址。
- ipconfig :查看本机 IP 192.168.12.42
- ping:测试连接 192.168.40.62
- 本地回路地址:127.0.0.1 255.255.255.255是广播地址
- IPv4:4个字节组成, 4个 0 - 255。大概 42 亿,30 亿都在北美,亚洲 4 亿。2011年初已经用尽。
- IPv6:8组,每组4个16进制数。相连的一段全是0 ,就可以省略,用 冒号代替
- 1a2b:0000:aaaa:0000:0000:0000:aabb:1f2f
- 1a2b::aaaa:0000:0000:0000:aabb:1f2f
- 1a2b:0000:aaaa::aabb:1f2f
- 1a2b:0000:aaaa::0000:aabb:1f2f
- 1a2b:0000:aaaa:0000::aabb:1f2f
端口
- 每个程序在设备上的唯一标识
- 每个网络程序都需要绑定一个端口号,传输数据的时候除了确定发到哪台机器上,还要明确发到哪个程序。
- 端口号范围从 0 - 65535
- 编写网络应用就需要绑定一个端口号,尽量使用 1024 以上的,1024 以下的基本上都被系统程序占用了。
- 常用端口
- mysql: 3306
- oracle: 1521
- web: 80
- tomcat: 8080
- QQ: 4000
- feiQ: 2425
协议: 为计算机网络中进行数据交换而建立的规则、标准或约定的集合。
- UDP:面向无连接,数据不安全,速度快。不区分客户端与服务端。
- TCP:面向连接(三次握手),数据安全,速度略低。分为客户端和服务端。
- 三次握手: 客户端先向服务端发起请求, 服务端响应请求, 传输数据
Socket套接字概述:
- 网络上具有唯一标识的 IP 地址和端口号组合在一起才能构成唯一能识别的标识符套接字。
- 通信的两端都有 Socket 。
- 网络通信其实就是 Socket 间的通信。
- 数据在两个 Socket 间通过 IO 流传输。
- Socket 在应用程序中创建,通过一种绑定机制与驱动程序建立关系,告诉自己所对应的 IP 和 port 。
UDP 传输:
发送Send
- 创建DatagramSocket, 随机端口号
- 创建DatagramPacket, 指定数据, 长度, 地址, 端口
- 使用DatagramSocket发送DatagramPacket
- 关闭DatagramSocket
接收Receive
- 创建DatagramSocket, 指定端口号
- 创建DatagramPacket, 指定数组, 长度
- 使用DatagramSocket接收DatagramPacket
关闭DatagramSocket
- 从DatagramPacket中获取数据
接收方获取ip和端口号
- String ip = packet.getAddress().getHostAddress();
- int port = packet.getPort();
TCP协议
客户端
- 创建 Socket 连接服务端(指定ip地址,端口号)通过ip地址找对应的服务器
- 调用Socket的 getInputStream() 和 getOutputStream() 方法获取和服务端相连的IO流
- 输入流可以读取服务端输出流写出的数据
- 输出流可以写出数据到服务端的输入流
服务端
- 创建ServerSocket(需要指定端口号)
- 调用ServerSocket的accept()方法接收一个客户端请求,得到一个Socket
- 调用Socket的getInputStream()和getOutputStream()方法获取和客户端相连的IO流
- 输入流可以读取客户端输出流写出的数据
- 输出流可以写出数据到客户端的输入流
- readLine() 是以 \r\n 为结束标志的
客户端
Socket socket = new Socket("127.0.0.1", 9999); //创建Socket指定ip地址和端口号 InputStream is = socket.getInputStream(); //获取输入流 OutputStream os = socket.getOutputStream(); //获取输出流 BufferedReader br = new BufferedReader(new InputStreamReader(is)); PrintStream ps = new PrintStream(os); System.out.println(br.readLine()); ps.println("wwwww"); System.out.println(br.readLine()); ps.println("ffffff"); socket.close();
服务端
ServerSocket server = new ServerSocket(9999); //创建服务器 Socket socket = server.accept(); //接受客户端的请求 InputStream is = socket.getInputStream(); //获取输入流 OutputStream os = socket.getOutputStream(); //获取输出流 BufferedReader br = new BufferedReader(new InputStreamReader(is)); PrintStream ps = new PrintStream(os); ps.println("ssssss"); System.out.println(br.readLine()); ps.println("xxxxx"); System.out.println(br.readLine()); server.close(); socket.close();
服务器多线程
ServerSocket server = new ServerSocket(9999); //创建服务器 while(true) { final Socket socket = server.accept(); //接受客户端的请求 new Thread() { public void run() { try { BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintStream ps = new PrintStream(socket.getOutputStream()); ps.println("yyyyy"); System.out.println(br.readLine()); ps.println("xxx"); System.out.println(br.readLine()); socket.close(); } catch (IOException e) { e.printStackTrace(); } } }.start();
dayt27 反射
- 类加载器:负责将 .class文件加载到对应的class 对象,并为之生成对应的 class 对象,
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
- 加载 :就是指将class文件读入内存,并为之创建一个Class对象。任何类被使用时系统都会建立一个Class对象
连接
- 验证 是否有正确的内部结构,并和其他类协调一致
- 准备 负责为类的静态成员分配内存,并设置默认初始化值
- 解析 将类的二进制数据中的符号引用替换为直接引用
- 初始化
类加载器的分类:
- Bootstrap ClassLoader 根类加载器,也被称为引导类加载器,负责Java核心类的加载,比如System,String等。在JDK中JRE的 lib 目录下 rt.jar 文件中
- Extension ClassLoader 扩展类加载器,负责JRE的扩展目录中jar包的加载。,在JDK中JRE的lib目录下ext目录
- System ClassLoader 系统类加载器,负责在 JVM 启动时加载来自 java 命令的class文件,以及classpath环境变量所指定的jar包和类路径。
- JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称 为 java 语言的反射机制。
通过 Object 类中的 getObject() 方法,创建对象阶段,判断是否是同一个字节码对象
Person p = new Person(); Class c = p.getClass();
通过 类名.class 获取到字节码文件对象,字节码阶段 xxx.class,当做静态方法的锁对象
Class c2 = Person.class;
通过 Class 类中的方法,源文件阶段 xxx.java,读取配置文件
Class c3 = Class.forName("Person");
- 前两种你必须明确 Person 类型,后面是指定这种类型的字符串就行,这种扩展更强,我不需要知道你的类.我只提供字符串,按照配置文件加载就可以了
在反射机制中,把类中的成员(构造方法、成员方法、成员变量)都封装成了对应的类进行表示。其中,构造方法使用类Constructor 表示。可通过Class类中提供的方法获取构造方法:
返回一个构造方法
- public Constructor
getConstructor(Class<?>... parameterTypes) 获取public修饰, 指定参数类型所对应的构造方法 - public Constructor
getDeclaredConstructor ( Class<?>... parameterTypes ) 获取指定参数类型所对应的构造方法(包含私有的)
- public Constructor
返回多个构造方法
- public Constructor<?>[] getConstructors() 获取所有的public 修饰的构造方法
- public Constructor<?>[] getDeclaredConstructors() 获取所有的构造方法(包含私有的)
在反射机制中,把类中的成员变量使用类 Field 表示。可通过 Class 类中提供的方法获取成员变量:
返回一个成员变量
- public Field getField(String name) 获取指定的 public 修饰的变量
- public Field getDeclaredField(String name) 获取指定的任意变量
返回多个成员变量
- public Field[] getFields() 获取所有 public 修饰的变量
- public Field[] getDeclaredFields() 获取所有的 变量 (包含私有)
在反射机制中,把类中的成员方法使用类Method表示。可通过Class类中提供的方法获取成员方法:
返回获取一个方法:
- public Method getMethod(String name, Class<?>... parameterTypes) ,获取public 修饰的方法
- public Method getDeclaredMethod(String name, Class<?>... parameterTypes),获取任意的方法,包含私有的 name 要查找的方法名称; 参数2: parameterTypes 该方法的参数类型
返回获取多个方法:
- public Method[] getMethods() 获取本类与父类中所有public 修饰的方法
- public Method[] getDeclaredMethods() 获取本类中所有的方法(包含私有的)
通过反射获取指定的方法
- public Object invoke(Object obj, Object... args) ,执行指定对象obj中,当前Method对象所代表的方法,方法要传入的参数通过args指定。
泛型只在编译期有效,在运行时会被擦除掉
ArrayList<Integer> list = new ArrayList<>() ; list.add(111) ; list.add(122) ; Class clazz = Class.forName("java.util.ArrayList") ; //获取字节码对象 Method m = clazz.getMethod("add",Object.class) ; //获取 get、方法 m.invoke(list, "abc") ; System.out.println(list);