Java基础复习(五)

时间:2023-02-17 12:34:13

该复习笔记是基于传智播客的Java基础视频-深入简出精华版,链接: https://pan.baidu.com/s/1bp7NuOJ,建议刚刚入门的小白不要看,因为涉及太多的细节了,看多了反而让你容易放弃Java之路,刚刚入门的小白推荐下面这一套视频-链接:https://pan.baidu.com/s/1pLuAj5x

博主声明一下:我不是传智播客的什么托,只是一个菜鸟,现在在补着Java基础,搞了一套传智播客的Java视频以及Android视频,所以笔记可能会常出现这些字眼,请言语讽刺我是托的麻烦你闭嘴哈

多线程(掌握)

  • 1.什么是线程
    • 线程是程序执行的一条路径, 一个进程中可以包含多条线程
    • 多线程并发执行可以提高程序的效率, 可以同时完成多项工作

Java多线程理解:Java多线程中的进程,线程,并行,并发 - 我所向往的美好 - 博客园

多线程并行和并发的区别(了解)

  • 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)
  • 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。
  • 比如我跟两个网友聊天,左手操作一个电脑跟甲聊,同时右手用另一台电脑跟乙聊天,这就叫并行。
  • 如果用一台电脑我先给甲发个消息,然后立刻再给乙发消息,然后再跟甲聊,再跟乙聊。这就叫并发。

Java程序运行原理和JVM的启动是多线程的吗(了解)

  • A:Java程序运行原理

    • Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。
  • B:JVM的启动是多线程的吗

    • JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。

多线程程序实现的方式1(掌握)

  • 1.继承Thread
    • 定义类继承Thread
    • 重写run方法
    • 把新线程要做的事写在run方法中
    • 创建线程对象
    • 开启新线程, 内部会自动执行run方法
public class Demo2_Thread {

public static void main(String[] args) {
// 4,创建Thread类的子类对象
MyThread mt = new MyThread();
// 5,开启线程
mt.start();
// 这个不是开启子线程的方法,只是在主线程中调用了该类的run方法而已
// mt.run();

for (int i = 0; i < 1000; i++) {
System.out.println("bb");
}
}

}

// 1,继承Thread
class MyThread extends Thread {
// 2,重写run方法
public void run() {
// 3,将要执行的代码写在run方法中
for (int i = 0; i < 1000; i++) {
System.out.println("aaaaaaaaaaaa");
}
}
}

多线程程序实现的方式2(掌握)

  • 2.实现Runnable
    • 定义类实现Runnable接口
    • 实现run方法
    • 把新线程要做的事写在run方法中
    • 创建自定义的Runnable的子类对象
    • 创建Thread对象, 传入Runnable
    • 调用start()开启新线程, 内部会自动调用Runnable的run()方法
public class Demo3_Thread {

public static void main(String[] args) {
//4,创建Runnable的子类对象
MyRunnable mr = new MyRunnable();
//Runnable target = mr; mr = 0x0011
//5,将其当作参数传递给Thread的构造函数
Thread t = new Thread(mr);
//6,开启线程
t.start();

for(int i = 0; i < 1000; i++) {
System.out.println("bb");
}
}

}

//1,定义一个类实现Runnable
class MyRunnable implements Runnable {
//2,重写run方法
@Override
public void run() {
//3,将要执行的代码写在run方法中
for(int i = 0; i < 1000; i++) {
System.out.println("aaaaaaaaaaaa");
}
}

}

实现Runnable的原理(了解)

  • 查看源码
    • 1,看Thread类的构造函数,传递了Runnable接口的引用
    • 2,通过init()方法找到传递的target给成员变量的target赋值
    • 3,查看run方法,发现run方法中有判断,如果target不为null就会调用Runnable接口子类对象的run方法

两种方式的区别(掌握)

  • 查看源码的区别:
    • a.继承Thread : 由于子类重写了Thread类的run(), 当调用start()时, 直接找子类的run()方法
    • b.实现Runnable : 构造函数中传入了Runnable的引用, 成员变量记住了它, start()调用run()方法时内部判断成员变量Runnable的引用是否为空, 不为空编译时看的是Runnable的run(),运行时执行的是子类的run()方法
  • 继承Thread
    • 好处是:可以直接使用Thread类中的方法,代码简单
    • 弊端是:如果已经有了父类,就不能用这种方法
  • 实现Runnable接口
    • 好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的
    • 弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代码复杂

Thread方法

  • 获取名字和设置名字
    • 1.获取名字
    • 通过getName()方法获取线程对象的名字
    • 2.设置名字
    • 通过构造函数可以传入String类型的名字
    • 通过setName(String)方法可以设置线程对象的名字
  • 获取当前线程的对象
    • Thread.currentThread(), 主线程也可以获取
  • 休眠线程
  • Thread.sleep(毫秒,纳秒), 控制当前线程休眠若干毫秒
    • 1秒= 1000毫秒
    • 1秒 = 1000 * 1000 * 1000纳秒(Win系统对这个支持不是很好,建议不要使用好一点)
  • 守护线程
    • 守护线程有点类型象棋里面的车马炮这些,当将军跪了,守护线程也跪了
    • setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出,方法设置为true意味着是守护线程
  • 加入线程
    • join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续(个人觉得应该是当一个小职员正开公家车,领导想开了,那小职员马上开回去给领导开,领导开完了再交给你开)
    • join(int), 可以等待指定的毫秒之后继续(个人理解:规定你插队指定的时间,在这个时间内你可以不被打扰做自己的事情)
  • 礼让线程(了解)
    • yield让出cpu,这个效果实际上很少可以达到的
  • 设置线程的优先级(了解)
    • setPriority()设置线程的优先级(范围是1-10,默认是5),这个效果也不是很明显,作为了解就好

同步

  • 同步代码块(掌握)
    • 1.什么情况下需要同步
      • 当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
      • 如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
    • 2.同步代码块
      • 使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块
      • 多个同步代码块如果使用相同的锁对象(锁对象可以是任意的,Object对象都可以的), 那么他们就是同步的
      • 锁对象不能用匿名对象,因为匿名对象不是同一个对象
  • 3.同步方法(掌握)
    • 使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的
    • 非静态的同步方法的锁对象是this,静态的同步方法的锁对象是该类的字节码对象(类名.class),不是类名.this
  • 4.线程安全问题(掌握)
    • 多线程并发操作同一数据时, 就有可能出现线程安全问题
    • 使用同步技术可以解决这种问题, 把操作数据的代码进行同步, 不要多个线程一起操作
  • 5.死锁(了解)
    • 多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁(在开发中尽量不要嵌套使用)

以前的线程安全的类回顾(掌握)

  • A:回顾以前说过的线程安全问题
    • 看源码:Vector,StringBuffer,Hashtable,Collections.synchroinzed(xxx)
    • Vector是线程安全的,ArrayList是线程不安全的
    • StringBuffer是线程安全的,StringBuilder是线程不安全的
    • Hashtable是线程安全的,HashMap是线程不安全的
    • Collections.synchroinzed(xxx)可以将线程不全的转为线程安全的

单例设计模式(掌握)

  • 单例设计模式:保证类在内存中只有一个对象。

  • 如何保证类在内存中只有一个对象呢?

    • (1)控制类的创建,不让其他类来创建本类的对象。private
    • (2)在本类中定义一个本类的对象。Singleton s;
    • (3)提供公共的访问方式。 public static Singleton getInstance(){return s}
  • 单例写法两种:
    • (1)饿汉式 开发用这种方式。
//饿汉式
class Singleton {
//1,私有构造方法,其他类不能访问该构造方法了
private Singleton(){}
//2,创建本类对象
private static Singleton s = new Singleton();//成员变量被私有,不能通过类名.调用
//3,对外提供公共的访问方法
public static Singleton getInstance() { //获取实例
return s;
}
}
  • (2)懒汉式 面试写这种方式。多线程会引发问题
//懒汉式(单例的延迟加载模式)
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;
}
}
  • 饿汉式和懒汉式的区别

    • 1,饿汉式是空间换时间(上来就创建了对象,虽然耗费了内存,但节约了时间),懒汉式是时间换空间(每次上来都要做一下判断是否为空,浪费了时间)
    • 2,在多线程访问时,饿汉式不会创建多个对象,而懒汉式有可能会创建多个对象

      • (3) final式(这种模式还没有名字,不如上面的那两种出名)
class Singleton {
//1,私有构造方法,其他类不能访问该构造方法了
private Singleton(){}
//2,final是最终的意思,被final修饰的变量不可以被更改
public static final Singleton s = new Singleton();

}
多线程之间的通信
两个线程间的通信(掌握)
  • 1.什么时候需要通信
    • 多个线程并发执行时, 在默认情况下CPU是随机切换线程的
    • 如果我们希望他们有规律的执行, 就可以使用通信, 例如每个线程执行一次打印
  • 2.怎么通信
    • 如果希望线程等待, 就调用wait()
    • 如果希望唤醒等待的线程, 就调用notify();
    • 这两个方法必须在同步代码中执行, 并且使用同步锁对象来调用
三个或三个以上间的线程通信
  • 多个线程通信的问题
    • notify()方法是随机唤醒一个线程
    • notifyAll()方法是唤醒所有线程(但每次叫醒所有的不好,毕竟有些不符合的条件的,建议使用互斥锁)
    • JDK5之前无法唤醒指定的一个线程
    • 如果多个线程之间通信, 需要使用notifyAll()通知所有线程, 用while来反复判断条件
      • if语句是在哪里等待,就在哪里起来;while循环是循环判断,每次都会判断标记
  • JDK1.5的新特性互斥锁(掌握)
  • 1.同步
    • 使用ReentrantLock类的lock()和unlock()方法进行同步
    • 其实该类就是为了替代synchronized的
  • 2.通信
    • 使用ReentrantLock类的newCondition()方法可以获取Condition对象
    • 需要等待的时候使用Condition的await()方法(谁想睡觉谁就调用), 唤醒的时候用signal()方法(谁想醒来谁就调用)
    • 不同的线程使用不同的Condition, 这样就能区分唤醒的时候找哪个线程了
多线程之间的通信注意事项:
  • 1,在同步代码块中,用哪个对象锁,就用哪个对象调用wait方法
  • 2,为什么wait方法和notify方法定义在Object这类中?
    • 因为锁对象可以是任意对象,Object是所有的类的基类,所以wait方法和notify方法需要定义在Object这个类中
  • 3,sleep方法和wait方法的区别?
    • a,sleep方法必须传入参数,参数就是时间,时间到了自动醒来
      wait方法可以传入参数也可以不传入参数,传入参数就是在参数的时间结束后等待,不传入参数就是直接等待
    • b,sleep方法在同步函数或同步代码块中,不释放锁,睡着了也抱着锁睡
      wait方法在同步函数或者同步代码块中,释放锁

线程组的概述和使用(了解)

  • A:线程组概述
    • Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
    • 默认情况下,所有的线程都属于主线程组。
      • public final ThreadGroup getThreadGroup()//通过线程对象获取他所属于的组
      • public final String getName()//通过线程组对象获取他组的名字
    • 我们也可以给线程设置分组
      • 1,ThreadGroup(String name) 创建线程组对象并给其赋值名字
      • 2,创建线程对象
      • 3,Thread(ThreadGroup?group, Runnable?target, String?name)
      • 4,设置整组的优先级或者守护线程



案例演示
1.线程组的使用,默认是主线程组

        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());

2.自己设定线程组

        // 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);

3.其中MyRunnable代码:

class MyRunnable implements Runnable {

@Override
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + "...." + i);
}
}

}

线程的五种状态(掌握)

  • 看图说话
    Java基础复习(五)
    stop()方式已经过时了

  • 新建,就绪,运行,阻塞,死亡

线程池的概述和使用(了解)

  • A:线程池概述
    • 程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
  • B:内置线程池的使用概述
    • JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
      • public static ExecutorService newFixedThreadPool(int nThreads)
      • public static ExecutorService newSingleThreadExecutor()
      • 这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法
      • Future
        //创建线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
//将线程放进池子里并执行
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());

//关闭线程池
pool.shutdown();

多线程程序实现的方式3(了解)

  • 提交的是Callable
public class Demo6_Callable {

public static void main(String[] args) throws InterruptedException, ExecutionException {
// 在线程池创建两个线程
ExecutorService pool = Executors.newFixedThreadPool(2);
// 将线程放进池子里并执行,使用Future接收结果
Future<Integer> f1 = pool.submit(new MyCallable(100));
Future<Integer> f2 = pool.submit(new MyCallable(50));

System.out.println(f1.get());
System.out.println(f2.get());

pool.shutdown(); // 关闭线程池
}

}

// 求1-num的和
class MyCallable implements Callable<Integer> {
private int num;

public MyCallable(int num) {
this.num = num;
}

@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= num; i++) {
sum += i;
}

return sum;
}

}
  • 多线程程序实现的方式3的好处和弊端

    • 好处:

      • 可以有返回值
      • 可以抛出异常
    • 弊端:

      • 代码比较复杂,所以一般不用

之前一种是Thread,第二种是Runnable,第三种是Callable:Java创建多线程的三种方法 - u012843873的博客- CSDN.NET

简单工厂模式概述和使用(了解)

  • A:简单工厂模式概述
    • 又叫静态工厂方法模式,它定义一个具体的工厂类负责创建一些类的实例
  • B:优点
    • 客户端不需要在负责对象的创建,从而明确了各个类的职责
  • C:缺点
    • 这个静态工厂类负责所有对象的创建,如果有新的对象增加,或者某些对象的创建方式不同,就需要不断的修改工厂类,不利于后期的维护
  • D:案例演示
    • 动物抽象类: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)) {return new Dog();}
else if(“cat”.equals(animale)) {
return new Cat();
}else {
return null;
}
}
}

工厂方法模式的概述和使用(了解)

  • A:工厂方法模式概述
    • 工厂方法模式中抽象工厂类负责定义创建对象的接口,具体对象的创建工作由继承抽象工厂的具体类实现。
  • B:优点
    • 客户端不需要在负责对象的创建,从而明确了各个类的职责,如果有新的对象增加,只需要增加一个具体的类和具体的工厂类即可,不影响已有的代码,后期维护容易,增强了系统的扩展性
  • C:缺点
    • 需要额外的编写代码,增加了工作量
  • D:案例演示

  • 动物抽象类:public abstract Animal { public abstract void eat(); }
    工厂接口:public interface Factory {public abstract Animal createAnimal();}
    具体狗类:public class Dog extends Animal {}
    具体猫类:public class Cat extends Animal {}
    开始,在测试类中每个具体的内容自己创建对象,但是,创建对象的工作如果比较麻烦,就需要有人专门做这个事情,所以就知道了一个专门的类来创建对象。发现每次修改代码太麻烦,用工厂方法改进,针对每一个具体的实现提供一个具体工厂。
    狗工厂:public class DogFactory implements Factory {
    public Animal createAnimal() {…}
    }
    猫工厂:public class CatFactory implements Factory {
    public Animal createAnimal() {…}
    }

适配器设计模式(掌握)

  • a.什么是适配器
    • 在使用监听器的时候, 需要定义一个类事件监听器接口.
    • 通常接口中有多个方法, 而程序中不一定所有的都用到, 但又必须重写, 这很繁琐.
    • 适配器简化了这些操作, 我们定义监听器时只要继承适配器, 然后重写需要的方法即可.
  • b.适配器原理
    • 适配器就是一个类, 实现了监听器接口, 所有抽象方法都重写了, 但是方法全是空的.
    • 适配器类需要定义成抽象的,因为创建该类对象,调用空方法是没有意义的
    • 目的就是为了简化程序员的操作, 定义监听器时继承适配器, 只重写需要的方法就可以了.

Java窗口(了解)

如何创建一个窗口并显示

  • 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);//设置窗体可见

布局管理器

  • FlowLayout(流式布局管理器)
    • 从左到右的顺序排列。
    • Panel默认的布局管理器。
  • BorderLayout(边界布局管理器)
    • 东,南,西,北,中
    • Frame默认的布局管理器。
  • GridLayout(网格布局管理器)
    • 规则的矩阵
  • CardLayout(卡片布局管理器)
    • 选项卡
  • GridBagLayout(网格包布局管理器)
    • 非规则的矩阵

窗体监听

Frame f = new Frame("我的窗体");
//事件源是窗体,把监听器注册到事件源上
//事件对象传递给监听器
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
//退出虚拟机,关闭窗口
System.exit(0);
}
});

需要知道的GUI知识

  • 事件处理
    • 事件: 用户的一个操作
    • 事件源: 被操作的组件
    • 监听器: 一个自定义类的对象, 实现了监听器接口, 包含事件处理方法,把监听器添加在事件源上, 当事件发生的时候虚拟机就会自动调用监听器中的事件处理方法

网络编程

网络编程概述(了解)

  • A:计算机网络
    • 是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。
  • B:网络编程
    • 就是用来实现网络互连的不同计算机上运行的程序间可以进行数据交换。

网络编程三要素之IP概述(掌握)

  • 每个设备在网络中的唯一标识
  • 每台网络终端在网络中都有一个独立的地址,我们在网络中传输数据就是使用这个地址。
  • ipconfig:查看本机IP192.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进制数。
  • 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
    • 面向连接(三次握手),数据安全,速度略低。分为客户端和服务端。
    • 三次握手: 客户端先向服务端发起请求, 服务端响应请求, 传输数据(类似你打电话给女神,必须等到女神接听才可以去表白)
    • HTTP协议是基于TCP协议的

Java网络编程相关知识:Java网络编程 - Ruthless - 博客园

Socket通信原理图解(了解)

  • A:Socket套接字概述:
    • 网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字。
    • 通信的两端都有Socket。
    • 网络通信其实就是Socket间的通信。
    • 数据在两个Socket间通过IO流传输。
    • Socket在应用程序中创建,通过一种绑定机制与驱动程序建立关系,告诉自己所对应的IP和port。

Java基础复习(五)

UDP传输(了解)

  • 1.发送Send
    • 创建DatagramSocket, 随机端口号
    • 创建DatagramPacket, 指定数据, 长度, 地址, 端口
    • 使用DatagramSocket发送DatagramPacket
    • 关闭DatagramSocket
  • 2.接收Receive
    • 创建DatagramSocket, 指定端口号
    • 创建DatagramPacket, 指定数组, 长度
    • 使用DatagramSocket接收DatagramPacket
    • 关闭DatagramSocket
    • 从DatagramPacket中获取数据
  • 3.接收方获取ip和端口号

    • String ip = packet.getAddress().getHostAddress();
    • int port = packet.getPort();
  • 4.案列演示:
    1.Send

        // 需要寄的物件
String str = "what are you 弄啥呢?";
//创建Socket相当于创建码头(相当于去快递公司寄快递,可以不填写发送人的信息)
DatagramSocket socket = new DatagramSocket();
//创建Packet相当于集装箱,
//物件装入快递(需要根据物件大小来选择快递),快递需要寄往127.0.0.1小区的6666房
DatagramPacket packet =
new DatagramPacket(str.getBytes(), str.getBytes().length,
InetAddress.getByName("127.0.0.1"), 6666);
//发货,将数据发出去(快递公司发送快递)
socket.send(packet);
//关闭码头,快递公司发出去了,可以收钱了,关闭这次的交易
socket.close(); //底层是io流,需要关闭释放资源

2.Receive

        //创建Socket相当于创建码头,但必须要用端口号,没有这个端口地址,货物就找不到目的地
//作为接收快递的一方,肯定需要有门牌号的
DatagramSocket socket = new DatagramSocket(6666);
//创建Packet相当于创建集装箱(接收快递,拿东西来装快递)
//第一个1024相当于有一个容量为1024的箱子,第二个这个1024的箱子最大可以装多少货物,前后两个数最好一致
DatagramPacket packet = new DatagramPacket(new byte[1024], 1024);
//接货,接收数据
socket.receive(packet);

//获取数据(拆快递)
byte[] arr = packet.getData();
//获取有效的字节个数
int len = packet.getLength();
System.out.println(new String(arr,0,len));
socket.close();//底层是io流,需要关闭释放资源
UDP传输优化
  • 1.发送端Send
        // 创建键盘录入对象
Scanner sc = new Scanner(System.in);
// 创建Socket相当于创建码头
DatagramSocket socket = new DatagramSocket();

// 获取键盘录入的字符串
while (true) {
String line = sc.nextLine();
if ("quit".equals(line)) {
break;
}
// 创建Packet相当于集装箱
DatagramPacket packet = new DatagramPacket(line.getBytes(),
line.getBytes().length,InetAddress.getByName("127.0.0.1"),
6666);
// 发货,将数据发出去
socket.send(packet);
}
// 关闭码头
socket.close();
  • 2.接收端Receive
        //创建Socket相当于创建码头
DatagramSocket socket = new DatagramSocket(6666);
//创建Packet相当于创建集装箱
DatagramPacket packet = new DatagramPacket(new byte[1024], 1024);

//接货,接收数据
while(true) {
socket.receive(packet);
//获取数据
byte[] arr = packet.getData();
//获取有效的字节个数
int len = packet.getLength();
//获取ip地址
String ip = packet.getAddress().getHostAddress();
//获取端口号
int port = packet.getPort();
System.out.println(ip + ":" + port + ":" + new String(arr,0,len));
}

TCP协议(掌握)

  • 1.客户端
    • 创建Socket连接服务端(指定ip地址,端口号)通过ip地址找对应的服务器
    • 调用Socket的getInputStream()和getOutputStream()方法获取和服务端相连的IO流
    • 输入流可以读取服务端输出流写出的数据
    • 输出流可以写出数据到服务端的输入流
  • 2.服务端
    • 创建ServerSocket(需要指定端口号)
    • 调用ServerSocket的accept()方法接收一个客户端请求,得到一个Socket
    • 调用Socket的getInputStream()和getOutputStream()方法获取和客户端相连的IO流
    • 输入流可以读取客户端输出流写出的数据
    • 输出流可以写出数据到客户端的输入流

Java基础复习(五)

TCP协议代码优化
  • 1.客户端
        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("我想报名就业班");
System.out.println(br.readLine());
ps.println("爷不学了");
socket.close();
  • 2.服务端
        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("欢迎咨询传智播客");
System.out.println(br.readLine());
ps.println("报满了,请报下一期吧");
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("欢迎咨询传智播客");
System.out.println(br.readLine());
ps.println("报满了,请报下一期吧");
System.out.println(br.readLine());
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
}

反射

类的加载概述和加载时机)

  • A:类的加载概述
    • 当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
    • 加载
      • 就是指将class文件读入内存,并为之创建一个Class对象。任何类被使用时系统都会建立一个Class对象。
    • 连接
      • 验证 是否有正确的内部结构,并和其他类协调一致
      • 准备 负责为类的静态成员分配内存,并设置默认初始化值
      • 解析 将类的二进制数据中的符号引用替换为直接引用
    • 初始化 就是我们以前讲过的初始化步骤
  • B:加载时机
    • 创建类的实例
    • 访问类的静态变量,或者为静态变量赋值
    • 调用类的静态方法
    • 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
    • 初始化某个类的子类
    • 直接使用java.exe命令来运行某个主类

类加载器的概述和分类

  • A:类加载器的概述
    • 负责将.class文件加载到内存中,并为之生成对应的Class对象。虽然我们不需要关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行。
  • B:类加载器的分类
    • Bootstrap ClassLoader 根类加载器
    • Extension ClassLoader 扩展类加载器
    • Sysetm ClassLoader 系统类加载器
  • C:类加载器的作用
    • Bootstrap ClassLoader 根类加载器
      • 也被称为引导类加载器,负责Java核心类的加载
      • 比如System,String等。在JDK中JRE的lib目录下rt.jar文件中
    • Extension ClassLoader 扩展类加载器
      • 负责JRE的扩展目录中jar包的加载。
      • 在JDK中JRE的lib目录下ext目录
    • Sysetm ClassLoader 系统类加载器
      • 负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径

Java类加载以及类加载器的概述:深入理解Java:类加载机制及反射 - 牛奶、不加糖 - 博客园

反射概述

  • A:反射概述

    • JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
    • 对于任意一个对象,都能够调用它的任意一个方法和属性;
    • 这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
    • 要想解剖一个类,必须先要获取到该类的字节码文件对象。
    • 而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象。
  • B:三种方式

    • a:Object类的getClass()方法,判断两个对象是否是同一个字节码文件
    • b:静态属性class,锁对象
    • c:Class类中静态方法forName(),读取配置文件
  • C:案例演示
    • 获取class文件对象的三种方式
      Java基础复习(五)
        Class clazz1 = Class.forName("com.heima.bean.Person");//包名.类名
Class clazz2 = Person.class;

Person p = new Person();
Class clazz3 = p.getClass();

System.out.println(clazz1 == clazz2);//true
System.out.println(clazz2 == clazz3);//true

Java反射机制详解:Java反射机制详解 - Java初级码农 - 博客园

Class.forName()读取配置文件举例

  • 榨汁机(Juicer)榨汁的案例
  • 分别有水果(Fruit)苹果(Apple)香蕉(Banana)桔子(Orange)榨汁(squeeze)

1.主要代码:

        public class Demo2_Reflect {

/**
* 榨汁机(Juicer)榨汁的案例
* 分别有水果(Fruit)苹果(Apple)香蕉(Banana)桔子(Orange)榨汁(squeeze)
* @throws Exception
*/

public static void main(String[] args) throws Exception {
/*Juicer j = new Juicer();
//j.run(new Apple());
j.run(new Orange());*/

BufferedReader br = new BufferedReader(new FileReader("config.properties")); //创建输入流对象,关联配置文件
Class<?> clazz = Class.forName(br.readLine()); //读取配置文件一行内容,获取该类的字节码对象
Fruit f = (Fruit) clazz.newInstance(); //通过字节码对象创建实例对象
Juicer j = new Juicer();
j.run(f);
}

}
interface Fruit {
public void squeeze();
}

class Apple implements Fruit {
public void squeeze() {
System.out.println("榨出一杯苹果汁儿");
}
}

class Orange implements Fruit {
public void squeeze() {
System.out.println("榨出一杯桔子汁儿");
}
}

class Juicer {
public void run(Fruit f) {
f.squeeze();
}

}

2.配置文件config.properties

com.heima.reflect.Apple

通过反射获取带参构造方法并使用

  • Constructor
    • Class类的newInstance()方法是使用该类无参的构造函数创建对象, 如果一个类没有无参的构造函数, 就不能这样创建了,可以调用Class类的getConstructor(String.class,int.class)方法获取一个指定的构造函数然后再调用Constructor类的newInstance(“张三”,20)方法创建对象
        //此时Person有一个Person(String name,int age)有参构造函数
Class clazz = Class.forName("com.heima.bean.Person");
//Person p = (Person) clazz.newInstance(); //通过无参构造创建对象
//System.out.println(p);

//获取有参构造
Constructor c = clazz.getConstructor(String.class,int.class);
//通过有参构造创建对象
Person p = (Person) c.newInstance("张三",23);
System.out.println(p);

其中Person对象主要代码:

package com.heima.bean;

public class Person {
private String name;
private int age;
public Person() {
super();

}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
//判断调用对象和传入对象的字节码文件是否是同一个字节码文件
if (this.getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}

public void eat() {
System.out.println("今天吃了一顿金钱豹");
}

public void eat(int num) {
System.out.println("今天吃了" + num + "顿金钱豹");
}

public void eat(int num,String status) {
System.out.println("今天吃了" + num + "顿金钱豹"+" ,"+"吃的"+status);
}
}

通过反射获取成员变量并使用

  • Field
    • Class.getField(String)方法可以获取类中的指定字段(可见的), 如果是私有的可以用getDeclaedField(“name”)方法获取,通过set(obj, “李四”)方法可以设置指定对象上该字段的值, 如果是私有的需要先调用setAccessible(true)设置访问权限,用获取的指定的字段调用get(obj)可以获取指定对象中该字段的值
        //此时Person有一个Person(String name,int age)有参构造函数
Class clazz = Class.forName("com.heima.bean.Person");
//获取有参构造
Constructor c = clazz.getConstructor(String.class,int.class);
//通过有参构造创建对象
Person p = (Person) c.newInstance("张三",23);

//Field f = clazz.getField("name"); //获取姓名字段
//f.set(p, "李四");//修改姓名的值,修改不了,因为name属性是私有的,需要使用暴力反射手段

//暴力反射获取字段(类似你想抢劫一个人的车)
Field f = clazz.getDeclaredField("name");
//去除私有权限(这里就类似你抢到那辆车的钥匙,接着你就可以做任何你想做的事情了)
f.setAccessible(true);
//修改p的姓名字段
f.set(p, "李四");

System.out.println(p);

通过反射获取方法并使用

  • Method
    • Class.getMethod(String, Class…) 和 Class.getDeclaredMethod(String, Class…)方法可以获取类中的指定方法,调用invoke(Object, Object…)可以调用该方法,Class.getMethod(“eat”) invoke(obj) Class.getMethod(“eat”,int.class) invoke(obj,10)
        Class clazz = Class.forName("com.heima.bean.Person");
//获取有参构造
Constructor c = clazz.getConstructor(String.class,int.class);
//通过有参构造创建对象
Person p = (Person) c.newInstance("张三",23);

//获取eat方法
Method m = clazz.getMethod("eat");
m.invoke(p);////今天吃了一顿金钱豹

//获取一个参数的eat方法
Method m2 = clazz.getMethod("eat", int.class);
m2.invoke(p, 10);//今天吃了10顿金钱豹

//获取两个参数的eat方法
Method m3 = clazz.getMethod("eat", int.class,String.class);
m3.invoke(p, 10,"不饱");//今天吃了10顿金钱豹 ,吃的不饱

泛型练习

  • A:案例演示:通过反射越过泛型检查
    • ArrayList的一个对象,在这个集合中添加一个字符串数据,如何实现呢?
    /**
* @param args
* ArrayList<Integer>的一个对象,在这个集合中添加一个字符串数据,如何实现呢?
* 泛型只在编译期有效,在运行期会被擦除掉
* @throws Exception
*/

public static void main(String[] args) throws Exception {
ArrayList<Integer> list = new ArrayList<>();
list.add(111);
list.add(222);

//获取字节码对象
Class clazz = Class.forName("java.util.ArrayList");
//获取add方法
Method m = clazz.getMethod("add", Object.class);
m.invoke(list, "abc");

System.out.println(list);

}
  • B:案例演示:通过反射写一个通用的设置某个对象的某个属性为指定的值
    • public void setProperty(Object obj, String propertyName, Object value){},此方法可将obj对象中名为propertyName的属性的值设置为value。

1.Tool工具类:

package com.heima.test;

import java.lang.reflect.Field;

public class Tool {
//此方法可将obj对象中名为propertyName的属性的值设置为value。
public void setProperty(Object obj, String propertyName, Object value) throws Exception {
Class clazz = obj.getClass(); //获取字节码对象
Field f = clazz.getDeclaredField(propertyName); //暴力反射获取字段
f.setAccessible(true); //去除权限
f.set(obj, value);
}
}

2.测试类

        Student s = new Student("张三", 23);
System.out.println(s);

Tool t = new Tool();
t.setProperty(s, "name", "李四");
System.out.println(s);
  • C:案例演示:按照要求做题
    • 已知一个类,定义如下:
      • package cn.itcast.heima;
      • public class DemoClass {
        public void run() {
        System.out.println(“welcome to heima!”);
        }
        }
      • (1) 写一个Properties格式的配置文件,配置类的完整名称。
      • (2) 写一个程序,读取这个Properties配置文件,获得类的完整名称并加载这个类,用反射的方式运行run方法。

1.DemoClass主要代码:

package com.heima.test;

public class DemoClass {
public void run() {
System.out.println("welcome to heima!");
}
}

2.xxx.properties

com.heima.test.DemoClass

3.测试代码

        //创建输入流关联xxx.properties
BufferedReader br = new BufferedReader(new FileReader("xxx.properties"));
//读取配置文件中类名,获取字节码对象
Class clazz = Class.forName(br.readLine());

//通过字节码对象创建对象
DemoClass dc = (DemoClass) clazz.newInstance();
dc.run();

动态代理的概述和实现

  • A:动态代理概述

    • 代理:本来应该自己做的事情,请了别人来做,被请的人就是代理对象。
    • 举例:春节回家买票让人代买
    • 动态代理:在程序运行过程中产生的这个对象,而程序运行过程中产生对象其实就是我们刚才反射讲解的内容,所以,动态代理其实就是通过反射来生成一个代理

    • 在Java中java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口就可以生成动态代理对象。JDK提供的代理只能针对接口做代理。我们有更强大的代理cglib,Proxy类中的方法创建动态代理类对象

    • public static Object newProxyInstance(ClassLoader loader,Class
public interface User {
void add();
void delete();

}

2.UserImp继承User接口:
我想每次使用add()方法以及delete方法之前需要权限验证,之后可以写到日志文件中,如果每个方法需要自己做,那么就会臃肿,这里建议使用动态代理模式来做

public class UserImp implements User {

@Override
public void add() {
//System.out.println("权限校验");
System.out.println("添加功能");
//System.out.println("日志记录");
}

@Override
public void delete() {
//System.out.println("权限校验");
System.out.println("删除功能");
//System.out.println("日志记录");
}

}

3.MyInvocationHandler

public class MyInvocationHandler implements InvocationHandler {
private Object target;

public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("权限校验");
method.invoke(target, args); //执行被代理target对象的方法
System.out.println("日志记录");
return null;
}

}

4.测试代码:

        UserImp ui = new UserImp();
ui.add();
ui.delete();

System.out.println("-------------------------------");

/*public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,
InvocationHandler h)
*/

MyInvocationHandler m = new MyInvocationHandler(ui);
User u = (User)Proxy.newProxyInstance(ui.getClass().getClassLoader(),
ui.getClass().getInterfaces(), m);
u.add();
u.delete();

模版(Template)设计模式概述和使用

  • A:模版设计模式概述
    • 模版方法模式就是定义一个算法的骨架,而将具体的算法延迟到子类中来实现
  • B:优点和缺点
    • a:优点
      • 使用模版方法模式,在定义算法骨架的同时,可以很灵活的实现具体的算法,满足用户灵活多变的需求
    • b:缺点
      • 如果算法骨架有修改的话,则需要修改抽象类
public class Demo1_Template {

/**
* @param args
*/

public static void main(String[] args) {
//这里做的话不符合我们Java面向对象的思想
/*long start = System.currentTimeMillis();
for(int i = 0; i < 1000000; i++) {
System.out.println("x");
}
long end = System.currentTimeMillis();
System.out.println(end - start);*/

Demo d = new Demo();
System.out.println(d.getTime());
}

}

abstract class GetTime {
//为了不让子类去重写这个方法
public final long getTime() {
long start = System.currentTimeMillis();
code();
long end = System.currentTimeMillis();
return end - start;
}

//如果在这里写代码,写死不好
public abstract void code();
}

class Demo extends GetTime {

@Override
public void code() {
int i = 0;
while(i < 1000) {
System.out.println("x");
i++;
}
}
}

目前已经学习的Java设计模式:
1,装饰
2,单例
3,简单工厂
4,工厂方法
5,适配器
6,模版

Java开发中的23种设计模式详解(转) - maowang - 博客园

JDK1.5新特性:

1,自动拆装箱
2,泛型
3,可变参数
4,静态导入
5,增强for循环
6,互斥锁
7,枚举

JDK5新特性之枚举类

  • A:枚举概述
    • 是指将变量的值一一列出来,变量的值只限于列举出来的值的范围内。举例:一周只有7天,一年只有12个月等。
  • B:回想单例设计模式:单例类是一个类只有一个实例
    • 那么多例类就是一个类有多个实例,但不是无限个数的实例,而是有限个数的实例。这才能是枚举类。
  • C:案例演示
    • 自己实现枚举类
public class Week {
//其实枚举就是单例设计模式的扩展
public static final Week MON = new Week();
public static final Week TUE = new Week();
public static final Week WED = new Week();

private Week(){} //私有构造,不让其他类创建本类对象
}
枚举的注意事项
  • A:案例演示
    • 定义枚举类要用关键字enum
    • 所有枚举类都是Enum的子类
    • 枚举类的第一行上必须是枚举项,最后一个枚举项后的分号是可以省略的,但是如果枚举类有其他的东西,这个分号就不能省略。建议不要省略
    • 枚举类可以有构造器,但必须是private的,它默认的也是private的。
    • 枚举类也可以有抽象方法,但是枚举项必须重写该方法
枚举类的常见方法
  • A:枚举类的常见方法
    • int ordinal()//枚举项都是有编号的,从0开始编号
    • int compareTo(E o)//比较的是编号
    • String name()//获取实例名称
    • String toString()//没有重写的话是获取实例名称
    • T valueOf(Class type,String name)//通过字节码对象获取枚举项
    • values() //此方法虽然在JDK文档中查找不到,但每个枚举类都具有该方法,它遍历枚举类的所有枚举值非常方便

JDK7新特性

  • A:二进制字面量
  • B:数字字面量可以出现下划线
  • C:switch 语句可以用字符串
  • D:泛型简化,菱形泛型
  • E:异常的多个catch合并,每个异常用或|
  • F:try-with-resources 语句

JDK8的新特性

  • 接口中可以定义有方法体的方法,如果是非静态,必须用default修饰(对象调用)
  • 如果是静态的就不用了(类名调用,不可以使用对象调用)
        class Test {
public void run() {
final int x = 10;
class Inner {
public void method() {
System.out.println(x);
}
}

Inner i = new Inner();
i.method();
}

}

问题:局部内部类在访问他所在方法中的局部变量必须用final修饰,为什么?
因为当调用这个方法时,局部变量如果没有用final修饰,他的生命周期和方法的生命周期是一样的,当方法弹栈,这个局部变量也会消失,那么如果局部内部类对象还没有马上消失想用这个局部变量,就没有了,如果用final修饰会在类加载的时候进入常量池,即使方法弹栈,常量池的常量还在,也可以继续使用

1.JDK1.5新特性:JDK 1.5 新特性 - 天幕个人博客 - 博客频道 - CSDN.NET
2.JDK1.6新特性:JDK 1.6 新特性 - 天幕个人博客 - 博客频道 - CSDN.NET
3.JDK1.7新特性:JDK 1.7 新特性 - 天幕个人博客 - 博客频道 - CSDN.NET
4.JDK1.8新特性:JDK 1.8 新特性 - 天幕个人博客 - 博客频道 - CSDN.NET

第四篇复习连接:
Java基础复习(四) - it菜鸟的飞行梦 - 博客频道 - CSDN.NET

没有下一篇了,JavaSE基础复习到此结业