当前位置:首页 > 公众号精选 > 架构师社区
[导读]Java是很多人一直在用的编程语言,但是有些Java概念是非常难以理解的,哪怕是一些多年的老手,对某些Java概念也存在一些混淆和困惑。 所以,这篇文章会介绍四个Java中最难理解的四个概念,去帮助开发者更清晰的理解这些概念。

Java 是很多人一直在用的编程语言,但是有些 Java 概念是非常难以理解的,哪怕是一些多年的老手,对某些 Java 概念也存在一些混淆和困惑。

所以,在这篇文章里,会介绍四个 Java 中最难理解的四个概念,去帮助开发者更清晰的理解这些概念:

  1. 匿名内部类的用法
  2. 多线程
  3. 如何实现同步
  4. 序列化

匿名内部类

匿名内部类又叫匿名类,它有点像局部类(Local Class)或者内部类(Inner Class),只是匿名内部类没有名字,我们可以同时声明并实例化一个匿名内部类。

一个匿名内部类仅适用在想使用一个局部类并且只会使用这个局部类一次的场景。

匿名内部类是没有需要明确声明的构造函数的,但是会有一个隐藏的自动声明的构造函数。

创建匿名内部类有两种办法:

  1. 通过继承一个类(具体或者抽象都可以)去创建出匿名内部类
  2. 通过实现一个接口创建出匿名内部类

咱们看看下面的例子:

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(); }}
  1. 构造匿名类时,我们可以使用任何构造函数。上面的代码可以看到我们使用了带参数的构造函数。
  2. 匿名类可以继承具体类或者抽象类,也能实现接口。所以访问修饰符规则同普通类是一样的。子类可以访问父类中的 protected 限制的属性,但是无法访问 private 限制的属性。
  3. 如果匿名类继承了具体类,比如上面代码中的 Programmer 类,那么就不必重写所有方法。但是如果匿名类继承了一个抽象类或者实现了一个接口,那么这个匿名类就必须实现所有没有实现的抽象方法。
  4. 在一个匿名内部类中你不能使用静态初始化,也没办法添加静态变量。
  5. 匿名内部类中可以有被 final 修饰的静态常量。

匿名类的典型使用场景

  1. 临时使用:我们有时候需要添加一些类的临时实现去修复一些问题或者添加一些功能。为了避免在项目里添加java文件,尤其是仅使用一次这个类的时候,我们就会使用匿名类。
  2. UI Event Listeners:在java的图形界面编程中,匿名类最常使用的场景就是去创建一个事件监听器。比如:
button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { }});

上面的代码中,我们通过匿名类实现了 setOnClickListener 接口,当用户点击按钮的时候,就会触发我们实现的 onClick 方法。

多线程

Java 中的多线程就是利用多个线程共同完成一个大任务的运行过程,使用多线程可以最大程度的利用CPU。

使用多线程的使用线程而不是进程来做任务处理,是因为线程比进程更加轻量,线程是一个轻量级的进程,是程序执行的最小单元,并且线程和线程之间是共享主内存的,而进程不是。

线程生命周期

什么?听说这四个概念,很多Java老手都说不清!

正如上图所示,线程生命周期一共有六种状态。我们现在依次对这些状态进行介绍。

  1. New:当我们构造出一个线程实例的时候, 这个线程就拥有了 New 状态。这个状态是线程的第一个状态。此时,线程并没有准备运行。
  2. Runnable:当调用了线程类的 start() 方法, 那么这个线程就会从 New 状态转换到 Runnable 状态。这就意味着这个线程要准备运行了。但是,如果线程真的要运行起来,就需要线程调度器来调度执行这个线程。但是线程调度器可能忙于在执行其他的线程,从而不能及时去调度执行这个线程。线程调度器是基于 FIFO 策略去从线程池中挑出一个线程来执行的。
  3. Blocked:线程可能会因为不同的情况自动的转为 Blocked 状态。比如,等候 I/O 操作,等候网络连接等等。除此之外,任意的优先级比当前正在运行的线程高的线程都可能会使得正在运行的线程转为 Blocked 状态。
  4. Waiting:在同步块中调用被同步对象的 wait 方法,当前线程就会进入 Waiting 状态。如果在另一个线程中的同一个对象被同步的同步块中调用 notify()/notifyAll(),就可能使得在 Waiting 的线程转入 Runnable 状态。
  5. Timed_Waiting:同 Waiting 状态,只是会有个时间限制,当超时了,线程会自动进入 Runnable 状态。
  6. Terminated:线程在线程的 run() 方法执行完毕后或者异常退出run()方法后,就会进入 Terminated 状态。

为什么要使用多线程

大白话讲就是通过多线程同时做多件事情让 Java 应用程序跑的更快,使用线程来实行并行和并发。如今的 CPU 都是多核并且频率很高,如果单独一个线程,并没有充分利用多核 CPU 的优势。

重要的优势

  • 可以更好地利用 CPU
  • 可以更好地提升和响应性相关的用户体验
  • 可以减少响应时间
  • 可以同时服务多个客户端

创建线程有两种方式

  1. 通过继承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(); } }}
  1. 通过实现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 对象,这个对象的状态在程序执行完毕或者退出后就消失了,不会得到保存。

所以,为了能解决这类问题,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

关键特性

  1. 如果父类实现了 Serializable 接口那么子类就不必再实现 Serializable 接口了。但是反过来不行。
  2. 序列化只支持非 static 的成员变量
  3. static 修饰的变量和常量以及被 transient 修饰的变量是不会被序列化的。所以,如果我们不想要序列化某些非 static 的成员变量,直接用 transient 修饰它们就好了。
  4. 当反序列化对象的时候,是不会调用对象的构造函数的。
  5. 如果一个对象被一个要序列化的对象引用了,这个对象也会被序列化,并且这个对象也必须要实现 Serializable 接口。

总结

首先,我们介绍了匿名类的定义,使用场景和使用方式。

其次,我们讨论了多线程和其生命周期以及多线程的使用场景。

再次,我们了解了同步,知道同步后,仅同时允许一个线程执行被同步的方法或者代码块。当一个线程在执行被同步的代码时,别的线程只能在队列中等待直到执行同步代码的线程释放资源。

最后,我们知道了序列化就是把对象状态储存起来以供后续使用。

特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:

什么?听说这四个概念,很多Java老手都说不清!

什么?听说这四个概念,很多Java老手都说不清!

什么?听说这四个概念,很多Java老手都说不清!

长按订阅更多精彩▼

什么?听说这四个概念,很多Java老手都说不清!

如有收获,点个在看,诚挚感谢

免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

9月2日消息,不造车的华为或将催生出更大的独角兽公司,随着阿维塔和赛力斯的入局,华为引望愈发显得引人瞩目。

关键字: 阿维塔 塞力斯 华为

加利福尼亚州圣克拉拉县2024年8月30日 /美通社/ -- 数字化转型技术解决方案公司Trianz今天宣布,该公司与Amazon Web Services (AWS)签订了...

关键字: AWS AN BSP 数字化

伦敦2024年8月29日 /美通社/ -- 英国汽车技术公司SODA.Auto推出其旗舰产品SODA V,这是全球首款涵盖汽车工程师从创意到认证的所有需求的工具,可用于创建软件定义汽车。 SODA V工具的开发耗时1.5...

关键字: 汽车 人工智能 智能驱动 BSP

北京2024年8月28日 /美通社/ -- 越来越多用户希望企业业务能7×24不间断运行,同时企业却面临越来越多业务中断的风险,如企业系统复杂性的增加,频繁的功能更新和发布等。如何确保业务连续性,提升韧性,成...

关键字: 亚马逊 解密 控制平面 BSP

8月30日消息,据媒体报道,腾讯和网易近期正在缩减他们对日本游戏市场的投资。

关键字: 腾讯 编码器 CPU

8月28日消息,今天上午,2024中国国际大数据产业博览会开幕式在贵阳举行,华为董事、质量流程IT总裁陶景文发表了演讲。

关键字: 华为 12nm EDA 半导体

8月28日消息,在2024中国国际大数据产业博览会上,华为常务董事、华为云CEO张平安发表演讲称,数字世界的话语权最终是由生态的繁荣决定的。

关键字: 华为 12nm 手机 卫星通信

要点: 有效应对环境变化,经营业绩稳中有升 落实提质增效举措,毛利润率延续升势 战略布局成效显著,战新业务引领增长 以科技创新为引领,提升企业核心竞争力 坚持高质量发展策略,塑强核心竞争优势...

关键字: 通信 BSP 电信运营商 数字经济

北京2024年8月27日 /美通社/ -- 8月21日,由中央广播电视总台与中国电影电视技术学会联合牵头组建的NVI技术创新联盟在BIRTV2024超高清全产业链发展研讨会上宣布正式成立。 活动现场 NVI技术创新联...

关键字: VI 传输协议 音频 BSP

北京2024年8月27日 /美通社/ -- 在8月23日举办的2024年长三角生态绿色一体化发展示范区联合招商会上,软通动力信息技术(集团)股份有限公司(以下简称"软通动力")与长三角投资(上海)有限...

关键字: BSP 信息技术
关闭
关闭