装修招标网站,展馆展厅设计报价,举报网站建设,wordpress首页分类调用Java 是很多人一直在用的编程语言#xff0c;但是有些 Java 概念是非常难以理解的#xff0c;哪怕是一些多年的老手#xff0c;对某些 Java 概念也存在一些混淆和困惑。所以#xff0c;在这篇文章里#xff0c;会介绍四个 Java 中最难理解的四个概念#xff0c;去帮助开发…Java 是很多人一直在用的编程语言但是有些 Java 概念是非常难以理解的哪怕是一些多年的老手对某些 Java 概念也存在一些混淆和困惑。所以在这篇文章里会介绍四个 Java 中最难理解的四个概念去帮助开发者更清晰的理解这些概念匿名内部类的用法多线程如何实现同步序列化匿名内部类匿名内部类又叫匿名类它有点像局部类(Local Class)或者内部类(Inner Class)只是匿名内部类没有名字我们可以同时声明并实例化一个匿名内部类。一个匿名内部类仅适用在想使用一个局部类并且只会使用这个局部类一次的场景。匿名内部类是没有需要明确声明的构造函数的但是会有一个隐藏的自动声明的构造函数。创建匿名内部类有两种办法:通过继承一个类(具体或者抽象都可以)去创建出匿名内部类通过实现一个接口创建出匿名内部类咱们看看下面的例子interface Programmer { void develop();}
public class TestAnonymousClass { public static Programmer programmer new Programmer() { Override public void develop() { System.out.println(我是在类中实现了接口的匿名内部类); } };public static void main(String[] args) { Programmer anotherProgrammer new Programmer() { Override public void develop() { System.out.println(我是在方法中实现了接口的匿名内部类); } };TestAnonymousClass.programmer.develop(); anotherProgrammer.develop(); }}从上面的例子可以看出匿名类既可以在类中也可以在方法中被创建。之前我们也提及匿名类既可以继承一个具体类或者抽象类也可以实现一个接口。所以在上面的代码里我创建了一个叫做 Programmer 的接口并在 TestAnonymousClass 这个类中和 main() 方法中分别实现了接口。Programmer除了接口以外既可以是一个抽象类也可以是一个具体类。抽象类像下面的代码一样public abstract class Programmer { public abstract void develop();}具体类代码如下:public class Programmer { public void develop() { System.out.println(我是一个具体类); }}OK继续深入那么如果 Programmer 这个类没有无参构造函数怎么办我们可以在匿名类中访问类变量吗我们如果继承一个类需要在匿名类中实现所有方法吗public class Programmer { protected int age;public Programmer(int age) { this.age age; }public void showAge() { System.out.println(年龄: age); }public void develop() { System.out.println(开发中……除了异性他人勿扰); }public static void main(String[] args) { Programmer programmer new Programmer(38) { Override public void showAge() { System.out.println(在匿名类中的showAge方法: age); } }; programmer.showAge(); }}
构造匿名类时我们可以使用任何构造函数。上面的代码可以看到我们使用了带参数的构造函数。匿名类可以继承具体类或者抽象类也能实现接口。所以访问修饰符规则同普通类是一样的。子类可以访问父类中的 protected 限制的属性但是无法访问 private 限制的属性。如果匿名类继承了具体类比如上面代码中的 Programmer 类那么就不必重写所有方法。但是如果匿名类继承了一个抽象类或者实现了一个接口那么这个匿名类就必须实现所有没有实现的抽象方法。在一个匿名内部类中你不能使用静态初始化也没办法添加静态变量。匿名内部类中可以有被 final 修饰的静态常量。匿名类的典型使用场景临时使用:我们有时候需要添加一些类的临时实现去修复一些问题或者添加一些功能。为了避免在项目里添加java文件尤其是仅使用一次这个类的时候我们就会使用匿名类。UI Event Listeners:在java的图形界面编程中匿名类最常使用的场景就是去创建一个事件监听器。比如:button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { }});上面的代码中我们通过匿名类实现了 setOnClickListener 接口当用户点击按钮的时候就会触发我们实现的 onClick 方法。多线程Java 中的多线程就是利用多个线程共同完成一个大任务的运行过程使用多线程可以最大程度的利用CPU。使用多线程的使用线程而不是进程来做任务处理是因为线程比进程更加轻量线程是一个轻量级的进程是程序执行的最小单元并且线程和线程之间是共享主内存的而进程不是。线程生命周期正如上图所示线程生命周期一共有六种状态。我们现在依次对这些状态进行介绍。New当我们构造出一个线程实例的时候, 这个线程就拥有了 New 状态。这个状态是线程的第一个状态。此时线程并没有准备运行。Runnable当调用了线程类的 start() 方法, 那么这个线程就会从 New 状态转换到 Runnable 状态。这就意味着这个线程要准备运行了。但是如果线程真的要运行起来就需要线程调度器来调度执行这个线程。但是线程调度器可能忙于在执行其他的线程从而不能及时去调度执行这个线程。线程调度器是基于 FIFO 策略去从线程池中挑出一个线程来执行的。Blocked线程可能会因为不同的情况自动的转为 Blocked 状态。比如等候 I/O 操作等候网络连接等等。除此之外任意的优先级比当前正在运行的线程高的线程都可能会使得正在运行的线程转为 Blocked 状态。Waiting在同步块中调用被同步对象的 wait 方法当前线程就会进入 Waiting 状态。如果在另一个线程中的同一个对象被同步的同步块中调用 notify()/notifyAll()就可能使得在 Waiting 的线程转入 Runnable 状态。Timed_Waiting同 Waiting 状态只是会有个时间限制当超时了线程会自动进入 Runnable 状态。Terminated线程在线程的 run() 方法执行完毕后或者异常退出run()方法后就会进入 Terminated 状态。为什么要使用多线程大白话讲就是通过多线程同时做多件事情让 Java 应用程序跑的更快使用线程来实行并行和并发。如今的 CPU 都是多核并且频率很高如果单独一个线程并没有充分利用多核 CPU 的优势。重要的优势可以更好地利用 CPU可以更好地提升和响应性相关的用户体验可以减少响应时间可以同时服务多个客户端创建线程有两种方式通过继承Thread类创建线程这个继承类会重写 Thread 类的 run() 方法。一个线程的真正运行是从 run() 方法内部开始的通过 start() 方法会去调用这个线程的 run() 方法。public class MultithreadDemo extends Thread { Override public void run() { try { System.out.println(线程 Thread.currentThread().getName() 现在正在运行); } catch (Exception e) { e.printStackTrace(); } }public static void main(String[] args) { for (int i 0; i 10; i) { MultithreadDemo multithreadDemo new MultithreadDemo(); multithreadDemo.start(); } }}
通过实现Runnable接口创建线程我们创建一个实现了 java.lang.Runnable 接口的新类并实现其 run() 方法。然后我们会实例化一个 Thread 对象并调用这个对象的 start() 方法。public class MultithreadDemo implements Runnable {Override public void run() { try { System.out.println(线程 Thread.currentThread().getName() 现在正在运行); } catch (Exception e) { e.printStackTrace(); } }public static void main(String[] args) { for (int i 0; i 10; i) { Thread thread new Thread(new MultithreadDemo()); thread.start(); } }}两种创建方式对比如果一个类继承了 Thread 类那么这个类就没办法继承别的任何类了。因为 Java 是单继承不允许同时继承多个类。多继承只能采用接口的方式一个类可以实现多个接口。所以使用实现 Runnable 接口在实践中比继承 Thread 类更好一些。第一种创建方式可以重写 yield()、interrupt() 等一些可能不太常用的方法。但是如果我们使用第二种方式去创建线程则 yield() 等方法就无法重写了。同步同步只有在多线程条件下才有意义一次只能有一个线程执行同步块。在 Java 中同步这个概念非常重要因为 Java 本身就是一门多线程语言在多线程环境中做合适的同步是极度重要的。为什么要使用同步在多线程环境中执行代码如果一个对象可以被多个线程访问为了避免对象状态或者程序执行出现错误对这个对象使用同步是非常必要的。在深入讲解同步概念之前我们先来看看同步相关的问题。class Production {//没有做方法同步 void printProduction(int n) { for (int i 1; i 5; i) { System.out.print(n * i ); try { Thread.sleep(400); } catch (Exception e) { System.out.println(e); } }}}
class MyThread1 extends Thread {Production p;MyThread1(Production p) { this.p p; }public void run() { p.printProduction(5); }
}
class MyThread2 extends Thread {Production p;MyThread2(Production p) { this.p p; }public void run() { p.printProduction(100); }}
public class SynchronizationTest { public static void main(String args[]) { Production obj new Production(); //多线程共享同一个对象 MyThread1 t1 new MyThread1(obj); MyThread2 t2 new MyThread2(obj); t1.start(); t2.start(); }}运行上面的代码后由于我们没有加同步可以看到运行结果非常混乱。Output:100 5 10 200 15 300 20 400 25 500接下来我们给 printProduction 方法加上同步class Production {//做了方法同步 synchronized void printProduction(int n) { for (int i 1; i 5; i) { System.out.print(n * i ); try { Thread.sleep(400); } catch (Exception e) { System.out.println(e); } }}}当我们对 printProduction() 加上了同步(synchronized)后, 已有一个线程执行的情况下是不会有任何一个线程可以再次执行这个方法。这次加了同步后的输出结果是有次序的。Output:5 10 15 20 25 100 200 300 400 500类似于对方法做同步你也可以去同步 Java 类和对象。注意其实有时候我们可以不必去同步整个方法。出于性能原因我们其实可以仅同步方法中我们需要同步的部分代码。被同步的这部分代码就是方法中的同步块。序列化Java 的序列化就是将一个 Java 对象转化为一个字节流的一种机制。从字节流再转回 Java 对象叫做反序列化是序列化的反向操作。序列化和反序列化是和平台无关的也就是说你可以在 Linux 系统序列化然后在 Windows 操作系统做反序列化。如果要序列化对象需要使用 ObjectOutputStream 类的 writeObject() 方法。如果要做反序列化则要使用 ObjectOutputStream 类的 readObject() 方法。如下图所示对象被转化为字节流后被储存在了不同的介质中。这个流程就是序列化。在图的右边也可以看到从不同的介质中比如内存获得字节流并转化为对象这叫做反序列化。为什么使用序列化如果我们创建了一个 Java 对象这个对象的状态在程序执行完毕或者退出后就消失了不会得到保存。所以为了能解决这类问题Java 提供了序列化机制。这样我们就能把对象的状态做临时储存或者进行持久化以供后续当我们需要这个对象时可以通过反序列化把对象还原回来。下面给出一些代码看看我们是怎么来做序列化的。import java.io.Serializable;
public class Player implements Serializable {private static final long serialVersionUID 1L;private String serializeValueName; private transient String nonSerializeValuePos;public String getSerializeValueName() { return serializeValueName; }public void setSerializeValueName(String serializeValueName) { this.serializeValueName serializeValueName; }public String getNonSerializeValueSalary() { return nonSerializeValuePos; }public void setNonSerializeValuePos(String nonSerializeValuePos) { this.nonSerializeValuePos nonSerializeValuePos; }Override public String toString() { return Player [serializeValueName serializeValueName ]; }}
import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;
public class SerializingObject { public static void main(String[] args) {Player playerOutput null; FileOutputStream fos null; ObjectOutputStream oos null;playerOutput new Player(); playerOutput.setSerializeValueName(niubi); playerOutput.setNonSerializeValuePos(x:1000,y:1000);try { fos new FileOutputStream(Player.ser); oos new ObjectOutputStream(fos); oos.writeObject(playerOutput);System.out.println(序列化数据被存放至Player.ser文件);oos.close(); fos.close(); } catch (IOException e) {e.printStackTrace(); } }}Output:序列化数据被存放至Player.ser文件import java.io.FileInputStream;import java.io.IOException;import java.io.ObjectInputStream;
public class DeSerializingObject {public static void main(String[] args) {Player playerInput null; FileInputStream fis null; ObjectInputStream ois null;try { fis new FileInputStream(Player.ser); ois new ObjectInputStream(fis); playerInput (Player) ois.readObject();System.out.println(从Player.ser文件中恢复);ois.close(); fis.close(); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); }System.out.println(player名字为 : playerInput.getSerializeValueName()); System.out.println(player位置为 : playerInput.getNonSerializeValuePos()); }}Output:从Player.ser文件中恢复player名字为 : niubiplayer位置为 : null关键特性如果父类实现了 Serializable 接口那么子类就不必再实现 Serializable 接口了。但是反过来不行。序列化只支持非 static 的成员变量static 修饰的变量和常量以及被 transient 修饰的变量是不会被序列化的。所以如果我们不想要序列化某些非 static 的成员变量直接用 transient 修饰它们就好了。当反序列化对象的时候是不会调用对象的构造函数的。如果一个对象被一个要序列化的对象引用了这个对象也会被序列化并且这个对象也必须要实现 Serializable 接口。总结首先我们介绍了匿名类的定义使用场景和使用方式。其次我们讨论了多线程和其生命周期以及多线程的使用场景。再次我们了解了同步知道同步后仅同时允许一个线程执行被同步的方法或者代码块。当一个线程在执行被同步的代码时别的线程只能在队列中等待直到执行同步代码的线程释放资源。最后我们知道了序列化就是把对象状态储存起来以供后续使用。