Java-基础语法合集
发布日期:2021-06-29 12:34:07 浏览次数:4 分类:技术文章

本文共 23816 字,大约阅读时间需要 79 分钟。

序言

本文旨在对Java SE的基础语法知识进行整理总结,故篇幅可能有点长。

1、接口与抽象类

对于面向对象编程来说,抽象是它的一大特征之一。在Java中,可以通过两种形式来体现OOP的抽象:接口和抽象类。这两者有太多相似的地方,又有太多不同的地方。

(1) 抽象类

  在了解抽象类之前,先来了解一下抽象方法。抽象方法是一种特殊的方法:它只有声明,而没有具体的实现。抽象方法的声明格式为:

abstract void fun();

抽象方法必须用abstract关键字进行修饰。如果一个类含有抽象方法,则称这个类为抽象类,抽象类必须在类前用abstract关键字修饰。因为抽象类中含有无具体实现的方法,所以不能用抽象类创建对象。

下面要注意一个问题:在《JAVA编程思想》一书中,将抽象类定义为“包含抽象方法的类”,但是后面发现如果一个类不包含抽象方法,只是用abstract修饰的话也是抽象类。也就是说抽象类不一定必须含有抽象方法。个人觉得这个属于钻牛角尖的问题吧,因为如果一个抽象类不包含任何抽象方法,为何还要设计为抽象类?所以暂且记住这个概念吧,不必去深究为什么。

[public] abstract class ClassName {    abstract void fun();}

从这里可以看出,抽象类就是为了继承而存在的,如果你定义了一个抽象类,却不去继承它,那么等于白白创建了这个抽象类,因为你不能用它来做任何事情。对于一个父类,如果它的某个方法在父类中实现出来没有任何意义,必须根据子类的实际需求来进行不同的实现,那么就可以将这个方法声明为abstract方法,此时这个类也就成为abstract类了。

  包含抽象方法的类称为抽象类,但并不意味着抽象类中只能有抽象方法,它和普通类一样,同样可以拥有成员变量和普通的成员方法。

注意,抽象类和普通类的主要有三点区别:

1、抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,   子类便无法实现该方法),缺省情况下默认为public。2、抽象类不能用来创建对象;3、如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现   父类的抽象方法,则必须将子类也定义为为abstract类。

在其他方面,抽象类和普通的类并没有区别。

(2) 接口

 接口,英文称作interface,在软件工程中,接口泛指供别人调用的方法或者函数。从这里,我们可以体会到Java语言设计者的初衷,它是对行为的抽象。在Java中,定一个接口的形式如下:

[public] interface InterfaceName {}

接口中可以含有变量和方法。但是要注意,接口中的变量会被隐式地指定为public static final变量(并且只能是public static final变量,用private修饰会报编译错误),而方法会被隐式地指定为public abstract方法且只能是public abstract方法(用其他关键字,比如private、protected、static、 final等修饰会报编译错误),并且接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法。从这里可以隐约看出接口和抽象类的区别,接口是一种极度抽象的类型,它比抽象类更加“抽象”,并且一般情况下不在接口中定义变量。

  要让一个类遵循某组特地的接口需要使用implements关键字,具体格式如下:

class ClassName implements Interface1,Interface2,[....]{
…………………………}

 可以看出,允许一个类遵循多个特定的接口。如果一个非抽象类遵循了某个接口,就必须实现该接口中的所有方法。对于遵循某个接口的抽象类,可以不实现该接口中的抽象方法。

(3) 接口 VS 抽象类

1、语法层面上的区别

(1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;(2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;(3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;(4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。

2、设计层面上的区别

(1)抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象

抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。举个简单的例子,飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。那么在设计的时候,可以将飞机设计为一个类Airplane,将鸟设计为一个类Bird,但是不能将 飞行 这个特性也设计为类,因此它只是一个行为特性,并不是对一类事物的抽象描述。此时可以将 飞行 设计为一个接口Fly,包含方法fly( ),然后Airplane和Bird分别根据自己的需要实现Fly这个接口。然后至于有不同种类的飞机,比如战斗机、民用飞机等直接继承Airplane即可,对于鸟也是类似的,不同种类的鸟直接继承Bird类即可。从这里可以看出,继承是一个 “是不是”的关系,而 接口 实现则是 “有没有”的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。

(2)抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计

什么是模板式设计?最简单例子,大家都用过ppt里面的模板,如果用模板A设计了ppt B和ppt C,ppt B和ppt C公共的部分就是模板A了,如果它们的公共部分需要改动,则只需要改动模板A就可以了,不需要重新对ppt B和ppt C进行改动。而辐射式设计,比如某个电梯都装了某种报警器,一旦要更新报警器,就必须全部更新。也就是说对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。

(4) 应用示例

下面看一个网上流传最广泛的例子:

门和警报的例子:门都有open( )和close( )两个动作,此时我们可以定义通过抽象类和接口来定义这个抽象概念

abstract class Door {    public abstract void open();    public abstract void close();}

或者:

interface Door {    public abstract void open();    public abstract void close();}

但是现在如果我们需要门具有报警alarm( )的功能,那么该如何实现?下面提供两种思路:

  (1)将这三个功能都放在抽象类里面,但是这样一来所有继承于这个抽象类的子类都具备了报警功能,但是有的门并不一定具备报警功能;

  (2)将这三个功能都放在接口里面,需要用到报警功能的类就需要实现这个接口中的open( )和close( ),也许这个类根本就不具备open( )和close( )这两个功能,比如火灾报警器。

从上面的讨论这里可以看出:

Door的open()、close()和alarm()根本就属于两个不同范畴内的行为,open()和close()属于门本身固有的行为特性,而alarm()属于延伸的附加行为。因此最好的解决办法是单独将报警设计为一个接口,包含alarm()行为,Door设计为单独的一个抽象类,包含open和close两种行为。再设计一个报警门继承Door类和实现Alarm接口。

代码如下:

interface Alram {
void alarm();}abstract class Door {
void open(); void close();}class AlarmDoor extends Door implements Alarm {
void oepn() { //.... } void close() { //.... } void alarm() { //.... }}

2、Java代码块

在java中用{ }括起来的称为代码块,代码块可分为以下四种:

1、普通代码块:类中方法的方法体。

2、构造代码块:也称“非静态代码块”。是用{……}直接包裹起来的代码块。构造块会在创建对象时被调用,每次创建时都会被调用,优先于类构造函数执行。
3、静态代码块:用static{……}包裹起来的代码片段,只会执行一次。静态代码块优先于构造块执行。
4、同步代码块:使用synchronized(){……}包裹起来的代码块,在多线程环境下,对共享数据的读写操作是需要互斥进行的,否则会导致数据的不一致性。同步代码块需要写在方法中。

(1) 静态块 VS 构造代码块

static{……}(静态代码块)与{……}(构造代码块/非静态代码块)的异同点:

相同点:(1)都是在JVM加载类时且在构造方法执行之前执行;(2)在类中都可以定义多个,执行顺序依照定义的先后顺序;(3)一般在代码块中对一些static变量进行赋值。不同点:(1)静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。(2)静态代码块只在第一次new执行一次,之后不再执行,而非静态代码块在每new一次就执行一次。(3)非静态代码块可在普通方法中定义(不过作用不大);而静态代码块不行。

来看个例子:

//普通类publicclass PuTong {    public PuTong(){        System.out.print("默认构造方法!-->");    }    //非静态代码块    {        System.out.print("非静态代码块!-->");    }    //静态代码块    static{        System.out.print("静态代码块!-->");    }    publicstaticvoid test(){        {            System.out.println("普通方法中的代码块!");        }    }}//测试类publicclass TestClass {     /**     * 区别两次new静态与非静态代码块执行情况     */    publicstaticvoid main(String[] args) {        PuTong c1 = new PuTong();        c1.test();        PuTong c2 = new PuTong();        c2.test();    }}

运行结果:

静态代码块!-->非静态代码块!-->默认构造方法!-->普通方法中的代码块!非静态代码块!-->默认构造方法!-->普通方法中的代码块!

构造代码块 与 构造函数 的区别

构造代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化,因为构造函数是可以多个的,运行哪个构造函数就会建立什么样的对象,但无论建立哪个对象,都会先执行相同的构造代码块。

也就是说,构造代码块中定义的是不同对象共性的初始化内容。所以理所当然的,构造代码块在构造函数之前执行。

(2) 静态块 VS 静态方法

一般情况下,如果有些代码必须在项目启动的时候就执行的时候,需要使用静态代码块,这种代码是主动执行的;

需要在项目启动的时候就初始化,在不创建对象的情况下,其他程序来调用的时候,需要使用静态方法,这种代码是被动执行的.

两者的区别就是:

1、静态代码块是自动执行的;2、静态方法是被调用的时候才执行的.

作用:

1、静态代码块可用来初始化一些项目最常用的变量或对象;2、静态方法可用作不创建对象也可能需要执行的代码。

(3) 详解静态代码块

在类中,可以将某一块代码声明为静态的,这样的程序块叫静态初始化段。

静态代码块的一般形式如下:

static {  语句序列}

静态代码块的特点:

1、静态代码块只能定义在类里面,它独立于任何方法,不能定义在方法里面。2、静态代码块里面的变量都是局部变量,只在本块内有效。3、静态代码块会在类被加载时自动执行,而无论加载者是JVM还是其他的类。4、一个类中允许定义多个静态代码块,执行的顺序根据定义的顺序进行。5、静态代码块只能访问类的静态成员,而不允许访问实例成员。

静态方法只能访问静态成员,实例方法可以访问静态和实例成员。之所以不允许静态方法访问实例成员变量,是因为实例成员变量是属于某个对象的,而静态方法在执行时,并不一定存在对象。

同样,因为实例方法可以访问实例成员变量,如果允许静态方法调用实例方法,将间接地允许它使用实例成员变量,所以它也不能调用实例方法。基于同样的道理,静态方法中也不能使用关键字this。

main()方法是一个典型的静态方法,它同样遵循一般静态方法的规则,所以它可以由系统在创建对象之前就调用

下面来看另一个静态块代码的例子:

public class staticBlock{   static int stMember = 100;    //定义静态成员变量   public static void main(String args[]){      System.out.println("This is main method.");   }   //第一个静态代码块   static{      System.out.println("This is first static block.");      stMember  = 200;      //访问静态成员变量      staticBlock oa = new staticBlock(); //创建对象      System.out.println("stMember = " + oa.stMember);      statFun();        //调用静态方法   }   //定义一个静态方法   static void statFun(){     System.out.println("This is a static method.");   }    //第二个静态代码块   static{     System.out.println("This is second static block.");   }}

程序运行的结果如下:

This is first static block.stMember = 200This is a static method.This is second static block.This is main method.

(4) 代码块执行顺序

代码的执行顺序:

1、主调类的静态代码块2、对象父类的静态代码块3、对象的静态代码块4、对象父类的非静态代码块5、对象父类的构造函数6、对象的非静态代码块7、对象的构造函数

示例代码

public class StaticBlockTest1 {    //主调类的非静态代码块    {        System.out.println("StaticBlockTest1 not static block");    }    //主调类的静态代码块    static {        System.out.println("StaticBlockTest1 static block");    }    public StaticBlockTest1(){        System.out.println("constructor StaticBlockTest1");    }    public static void main(String[] args) {        Children children = new Children();        children.getValue();    }}class Parent{    private String name;    private int age;    //父类无参构造方法    public Parent(){        System.out.println("Parent constructor method");        {            System.out.println("Parent constructor method--> not static block");        }    }    //父类的非静态代码块    {        System.out.println("Parent not static block");        name = "zhangsan";        age = 50;    }    //父类静态代码块    static {        System.out.println("Parent static block");    }    //父类有参构造方法    public Parent(String name, int age) {        System.out.println("Parent constructor method");        this.name = "lishi";        this.age = 51;    }    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;    }}class Children extends Parent{    //子类静态代码块    static {        System.out.println("Children static block");    }    //子类非静态代码块    {        System.out.println("Children not static block");    }    //子类无参构造方法    public Children(){        System.out.println("Children constructor method");    }    public void getValue(){        //this.setName("lisi");        //this.setAge(51);        System.out.println(this.getName() + this.getAge());    }}

输出结果

StaticBlockTest1 static block   //主调类的静态代码块Parent static block             //父类的静态代码块Children static block           //子类的静态代码块Parent not static block         //父类的非静态代码块Parent constructor method       //父类的构造函数Parent constructor method--> not static blockChildren not static block       //主调类的非静态代码块Children constructor method     //主调类的构造函数zhangsan50                      //主调main方法

3、深入String

工作中很多时候都会用到String,关于字符串的操作有StringStringBufferStringBuilder三种形式。

这三个类在字符串处理中的简单对比:

1.三者在执行速度方面的比较:StringBuilder >  StringBuffer  >  String2.String <(StringBuffer,StringBuilder)的原因:   (1)String:字符串常量(不可变),注意是常量!!!不是变量!!   (2)StringBuffer:字符创变量(线程安全)   (3)StringBuilder:字符创变量(非线程安全)

下面详细解析这三个类的内部细节和区别。

(1) String

String:字符串常量,字符串长度不可变。Java中String是immutable(不可变)的。

Java中String类的成员变量包含如下定义:

/** The value is used for character storage. */private final char value[];/** The offset is the first index of the storage that is used. */private final int offset;/** The count is the number of characters in the String. */private final int count;

可见,用于存放字符的数组被声明为final的,因此只能赋值一次,不可再更改

(2) StringBuffer

StringBuffer:字符串变量(Synchronized,即线程安全)。如果要频繁对字符串内容进行修改,出于效率考虑最好使用StringBuffer,如果想转成String类型,可以调用StringBuffer的toString()方法。

Java.lang.StringBuffer线程安全的可变字符序列。在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。可将字符串缓冲区安全地用于多个线程。

StringBuffer 上的主要操作是 appendinsert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。

append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。例如,如果 z 引用一个当前内容是“start”的字符串缓冲区对象,则

1、此方法调用 z.append("le") 会使字符串缓冲区包含“startle”,2、而 z.insert(4, "le") 将更改字符串缓冲区,使之包含“starlet”。

(3) StringBuilder

StringBuilder:字符串变量(非线程安全)。在内部,StringBuilder对象被当作是一个包含字符序列的变长数组。在大部分情况下,StringBuilder的效率高于StringBuffer,这主要是由于前者不需要考虑线程安全。

java.lang.StringBuilder是一个可变的字符序列,是JDK5.0新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。

其构造方法如下:

这里写图片描述

(4) 三者区别

String 类型和StringBuffer的主要性能区别:

String是不可变的对象, 因此在每次对String类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的String 对象,所以经常改变内容的字符串最好不要用 String,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,性能就会降低。

使用 StringBuffer 类时,每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。所以多数情况下推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下。

在某些特别情况下,String 对象的字符串拼接其实是被 Java Compiler 编译成了 StringBuffer 对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,例如:

String s1 = “This is only a” + “ simple” + “ test”;StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);

生成 String s1对象的速度并不比 StringBuffer慢。其实在Java Compiler里,自动做了如下转换:

Java Compiler直接把上述第一条语句编译为:

String s1 = “This is only a simple test”;

所以速度很快。但要注意的是,如果拼接的字符串来自另外的String对象的话,Java Compiler就不会自动转换了,速度也就没那么快了,例如:

String s2 = “This is only a”;String s3 = “ simple”;String s4 = “ test”;String s1 = s2 + s3 + s4;

这时候,Java Compiler会规规矩矩的按照原来的方式去做,String的concatenation(即+)操作利用了StringBuilder(或StringBuffer)的append方法实现,此时,对于上述情况,若s2,s3,s4采用String定义,拼接时需要额外创建一个StringBuffer(或StringBuilder),之后将StringBuffer转换为String;若采用StringBuffer(或StringBuilder),则不需额外创建StringBuffer。

(5) 使用策略

A、基本原则:

1、如果要操作少量的数据,用String;2、单线程操作大量数据,用StringBuilder;3、多线程操作大量数据,用StringBuffer。

B、不要使用String类的”+”来进行频繁的拼接,因为那样的性能极差的,应该使用StringBuffer或StringBuilder类,这在Java的优化上是一条比较重要的原则。

C、为了获得更好的性能,在构造 StringBuffer 或 StringBuilder 时应尽可能指定它们的容量。当然,如果你操作的字符串长度(length)不超过 16 个字符就不用了,当不指定容量(capacity)时默认构造一个容量为16的对象。不指定容量会显著降低性能。

D、相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。而在现实的模块化编程中,负责某一模块的程序员不一定能清晰地判断该模块是否会放入多线程的环境中运行,因此:除非确定系统的瓶颈是在 StringBuffer 上,并且确定你的模块不会运行在多线程模式下,才可以采用StringBuilder;否则还是用StringBuffer。


4、Instanceof关键字

在比较一个类是否和另一个类属于同一个类实例的时候,我们通常可以采用instanceof和getClass两种方法通过两者是否相等来判断

但是两者在判断上面是有差别的,下面从代码中看看区别:

class Parent {
}class Child extends Parent {
}public class Test{
public static void testInstanceof(Object x) { System.out.println("x instanceof Parent: "+(x instanceof Parent)); System.out.println("x instanceof Child: "+(x instanceof Child)); System.out.println("x getClass Parent: "+(x.getClass() == Parent.class)); System.out.println("x getClass Child: "+(x.getClass() == Child.class)); } public static void main(String[] args) { testInstanceof(new Parent()); System.out.println("---------------------------"); testInstanceof(new Child()); }}

运行结果:

x instanceof Parent:  truex instanceof Child:  falsex getClass Parent:  truex getClass Child:  false---------------------------x instanceof Parent:  truex instanceof Child:  truex getClass Parent:  falsex getClass Child:  true

结论:

从程序的结果可以看出,

instanceof 进行类型检查规则是:你属于该类吗?或者你属于该类的子类吗?

而通过getClass获得类型信息采用==来进行检查是否相等的操作是严格的判断,不会存在继承方面的考虑。


5、自动装箱与拆箱

1、装箱就是自动将 基本数据类型 转换为 包装器类型

2、拆箱就是自动将 包装器类型 转换为 基本数据类型

(1) Java数据类型

Java中的数据类型分为两类:一类是基本数据类型,另一类是引用数据类型

如下图:

这里写图片描述
由上可知,

Java中的基本数据类型有八种分别是:  int(4字节)    byte(1字节)   short(2字节)  long(8字节)   float(4字节)  double(8字节)   char(2字节)   boolean(1bit)基本数据类型不是对象,不能使用对象的方法。

将基本数据类型转换为对象就是自动装箱的过程。下面是基本数据类型与封装器类之间对应的关系:

这里写图片描述

(2) 自动装箱

在Java SE5之前,如果要生成一个数值为10的Integer对象,必须这样进行:

Integer i = new Integer(10);

而在从Java SE5开始就提供了自动装箱的特性,如果要生成一个数值为10的Integer对象,只需要这样就可以了:

//自动装箱 Integer i = 10;

这个过程中会自动根据数值创建对应的 Integer对象,这就是装箱。

自动装箱过程中,使用的时Integer的valueOf()方法:

这里写图片描述

上面代码可以发现,在通过valueOf()方法创建Integer对象的时候,如果数值在[-128,127]之间,便返回指向 IntegerCache.cache(Integer的缓存机制)中已经存在的对象的引用;否则创建一个新的Integer对象

Integer的缓存机制

从Java1.5开始,Integer类型是有缓存对象的。缓存对象IntegerCache是Integer中的一个静态内部类,缓存的范围是 -128~127

具体实现代码如下:

这里写图片描述

(3) 自动拆箱

那什么是拆箱呢?顾名思义,跟装箱对应,就是自动将包装器类型转换为基本数据类型:

//自动装箱Integer a1 = 10;  //自动拆箱int  a2 = a1; //系统就会将Integer类型的a1自动拆箱,把值取出来赋值给a2

这个时候系统自动调用的是Integer的intValue()方法:

这里写图片描述

(4) “==” 和 equals()方法

比较符“ == ”

1、对于基本类型,不包含基本类型的包装类型,==比较的是基本类型的值

2、对于引用类型(对象)来说,== 比较的是两个对象在内存中的地址

equals()方法

equals()是Object对象中的方法:

这里写图片描述

这是在Object对象中的实现,因为所有的类都继承于Object类,一般都会重写这个方法,实现不同,比较的方式就不同,这个方法主要就是实现对象在逻辑上的相等,而这个逻辑是由你决定的。

下面是Integer类中的equals()方法:

这里写图片描述
这里首先判断比较的对象是否是Integer,如果是则强制转换成Integer,然后比较它们的值,如果类型不同直接返回false。

注意,如果在自定义的类中你没有重写Object类的equals()方法,那么你的类的对象(引用类型)使用equals()方法时,将比较的是两个对象的内存地址!!

举个很现实的例子,比如此时你定义的是一个person类,你希望通过身份证号(ID)来判定两个person对象是否是同一个人(这需求很正常吧)。如果你不在类中重写equals()方法,将比较的内容由默认的内存地址改为ID,那么你将没法实现正常的身份判定!

(5) 综合示例

结合上面所述知识,花点心思思考下下面代码的运行结果是什么,再与答案对比一下。

public class AutoboxingTest {
public static void main(String[] args) { Integer a = 1; Integer b = 2; Integer c = 3; Integer d = 3; Integer e = 321; Integer f = 321; Long g = 3L; /***************此例十分重要!进入思考时间************/ System.out.println(c == d); //true.因为c和d都是缓存对象3(小于127),就是同一个对象,内存地址唯一,所以相等。 System.out.println(e == f); //false.因为e和f都超出缓存对象的范围,都是new的新的对象,是不同的对象所以在内存中的地址肯定不同,所以是false System.out.println(c == (a + b)); //true.a+b的值为3,依旧为缓存对象,与c指向同一内存地址。 System.out.println(c.equals(a + b)); //true.Integer对象的equals()方法,比较实际的数值大小,不是内存地址 System.out.println(g == (a + b)); //true.“==”对基本类型的数值比较。期间还会发生隐式类型转换,int → long。 System.out.println(g.equals(a + b)); //false.Long属于基本类型,没有重写equals()方法,比较的是默认的内存地址 }}

正确答案(运行结果):

truefalsetruetruetruefalse

6、Java内部类

内部类(inner class)是定义在另一个类中的类,内部类中的方法可以访问创建该内部类的类(我们称其为外围类 outer class)的域中所有数据(包括私有/private数据)。并且,内部类可以对同一个包中的其他类隐藏起来

但是由于内部类是一种编译器现象,在虚拟机中只存在常规类文件,所以在编译过程中,内部类被翻译成”外围类名$内部类名”的一个常规类,另一方面由于内部类有非常高的特权(可以访问外围类中的私有域)所以在一些特殊手段下(比如利用十六进制编辑器创建一个虚拟机指令调用该内部类),有可能会导致外围类内的私有域数据泄露。

在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。广泛意义上的内部类一般来说包括这四种:成员内部类局部内部类匿名内部类静态内部类。下面就先来了解一下这四种内部类的用法。

(1) 成员内部类

  成员内部类是最普通的内部类,它的定义为位于另一个类的内部,形如下面的形式:

class Circle {    double radius = 0;    public Circle(double radius) {        this.radius = radius;    }    class Draw {     //内部类        public void drawSahpe() {            System.out.println("drawshape");        }    }}

这样看起来,类Draw像是类Circle的一个成员,Circle称为外部类。成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)

class Circle {    private double radius = 0;    public static int count =1;    public Circle(double radius) {        this.radius = radius;    }    class Draw {     //内部类        public void drawSahpe() {            System.out.println(radius);  //外部类的private成员            System.out.println(count);   //外部类的静态成员        }    }}

不过要注意的是,当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:

外部类.this.成员变量外部类.this.成员方法

 虽然成员内部类可以无条件地访问外部类的成员,而外部类想访问成员内部类的成员却不是这么随心所欲了。在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问:

class Circle {    private double radius = 0;    public Circle(double radius) {        this.radius = radius;        getDrawInstance().drawSahpe();   //必须先创建成员内部类的对象,再进行访问    }    private Draw getDrawInstance() {        return new Draw();    }    class Draw {     //内部类        public void drawSahpe() {            System.out.println(radius);  //外部类的private成员        }    }}

成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。创建成员内部类对象的一般方式如下:

public class Test {    public static void main(String[] args)  {        //第一种方式:        Outter outter = new Outter();        Outter.Inner inner = outter.new Inner();  //必须通过Outter对象来创建        //第二种方式:        Outter.Inner inner1 = outter.getInnerInstance();    }}class Outter {    private Inner inner = null;    public Outter() {    }    public Inner getInnerInstance() {        if(inner == null)            inner = new Inner();        return inner;    }    class Inner {        public Inner() {        }    }}

内部类可以拥有private访问权限、protected访问权限、public访问权限及包访问权限。比如上面的例子,

1、如果成员内部类Inner用private修饰,则只能在外部类的内部访问;2、如果用public修饰,则任何地方都能访问;3、如果用protected修饰,则只能在同一个包下或者继承外部类的情况下访问;4、如果是默认访问权限,则只能在同一个包下访问。

这一点和外部类有一点不一样,外部类只能被public和包访问两种权限修饰。我个人是这么理解的,由于成员内部类看起来像是外部类的一个成员,所以可以像类的成员一样拥有多种权限修饰

(2) 局部内部类

局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。

class People{
public People() { }}class Man{
public Man(){ } public People getWoman(){ class Woman extends People{
//局部内部类 int age =0; } return new Woman(); }}

 注意,局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。

(3) 静态内部类

  静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。

public class Test {    public static void main(String[] args)  {        Outter.Inner inner = new Outter.Inner();    }}class Outter {    public Outter() {    }    static class Inner {        public Inner() {        }    }}

这里写图片描述

(4) 匿名内部类

匿名内部类也就是没有名字的内部类。正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写。但使用匿名内部类还有个前提条件:必须继承一个抽象父类或实现一个接口

咱们直接来看例子:

实例1:不使用匿名内部类来实现抽象方法

abstract class Person {
public abstract void eat();}class Child extends Person {
public void eat() { System.out.println("eat something"); }}public class Demo {
public static void main(String[] args) { Person p = new Child(); p.eat(); }}运行结果:eat something

可以看到,我们用Child继承了Person类,然后实现了Child的一个实例,将其向上转型为Person类的引用。但是,如果此处的Child类只使用一次,那么将其编写为独立的一个类岂不是很麻烦?这个时候就引入了匿名内部类。

实例2:匿名内部类的基本实现

abstract class Person {    public abstract void eat();}public class Demo {    public static void main(String[] args) {        Person p = new Person() {            public void eat() {                System.out.println("eat something");            }        };        p.eat();    }}运行结果:eat something

可以看到,我们直接将抽象类Person中的方法在大括号中实现了。这样便可以省略一个类的书写,并且,匿名内部类还能用于接口上。

实例3:在接口上使用匿名内部类

interface Person {    public void eat();}public class Demo {    public static void main(String[] args) {        Person p = new Person(){            public void eat(){                System.out.println("eat something");            }        };        p.eat();    }}运行结果:eat something

由上面的例子可以看出,只要一个类是抽象的或是一个接口,那么其子类中的方法都可以使用匿名内部类来实现

最常用的情况就是在多线程的实现上,因为要实现多线程必须继承Thread类或是继承Runnable接口。

实例4:Thread类的匿名内部类实现

public class Demo {    public static void main(String[] args) {        //Thread类的匿名内部类实现        Thread t = new Thread() {            public void run(){                for (int i= 1;i <= 5;i++) {                    System.out.print(i+ " ");                }            }        };        t.start();    }}运行结果:1 2 3 4 5

实例5:Runnable接口的匿名内部类实现

public class Demo {    public static void main(String[] args) {        //Runnable接口的匿名内部类实现        Runnable r = new Runnable() {            public void run() {                for (int i = 1; i <= 5; i++) {                    System.out.print(i + " ");                }            }        };        Thread t = new Thread(r);        t.start();    }}运行结果:1 2 3 4 5

了解更多关于匿名内部类:


7、Lambda表达式

Java8其中一个很重要的新特性就是lambda表达式,允许我们将行为传到函数中。想想看,在Java8之前我们想要将行为传入函数,仅有的选择就是匿名内部类

Java8发布以后,lambda表达式将大量替代匿名内部类的使用,简化代码的同时,更突出了原来匿名内部类中最重要的那部分包含真正逻辑的代码。

基本语法:

(parameters) -> expression 或(parameters) ->{   statements; }

即: 参数 -> 带返回值的表达式/无返回值的陈述

//1. 接收2个int型整数,返回他们的和(int x, int y) -> x + y;//2. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)(String s) -> System.out.print(s);

(1) Lambda表达式实现Runnable

毫无疑问,lambda表达式用得最多的场合就是替代匿名内部类,而实现Runnable接口是匿名内部类的经典例子。lambda表达式的功能相当强大,用()->就可以代替整个匿名内部类!请看代码:

如果使用匿名内部类:

@Test    public void oldRunable() {        new Thread(new Runnable() {            @Override            public void run() {                System.out.println("The old runable now is using!");            }        }).start();    }

而如果使用lambda表达式:

@Test    public void runable() {        new Thread(() -> System.out.println("It's a lambda function!")).start();    }

上述代码中:

() -> System.out.println(“I am running”)就是一个lambda表达式,可以看出,它是替代了new Runnable(){}这个匿名内部类。

编译器负责推导lambda表达式的类型。它利用lambda表达式所在上下文所期待的类型进行推导,这个被期待的类型被称为目标类型。就是说我们传入的参数可以无需写类型了!

最后的输出:

The old runable now is using!It's a lambda function!

是不是强大到可怕?是不是简单到可怕?是不是清晰明了重点突出到可怕?这就是lambda表达式的可怕之处,用极少的代码完成了之前一个类做的事情!

(2) Lambda表达式实现Comparator

import java.util.Arrays;import java.util.Collections;import java.util.Comparator;import java.util.List;public class SortList {
//给入一个List private static List
list = Arrays.asList("my","name","is","uber","and","uc"); /** * 对一个String的list进行排序 - 使用老方法 */ public static void oldSort(){ //排序 Collections.sort(list,new Comparator
() { //使用新的排序规则 根据第二个字符进行逆序排 @Override public int compare(String a,String b){ if (a.charAt(1) <= b.charAt(1)) { return 1; }else{ return -1; } } }); } /** * 新的排序方法 - 使用lambda表达式实现 */ public static void newSort(){ //lambda会自动推断出 a,b 的类型 Collections.sort(list, (a, b) -> a.charAt(1) < b.charAt(1) ? 1:-1); } public static void main(String[] args) { //oldSort(); newSort(); }}

(3) Lambda表达式实现ActionListener

import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import javax.swing.JButton;public class ActionEventDemo {
private JButton button = new JButton(); public void bindEvent(){ button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.out.println("你好!" ); } }); } /** * 使用Lambda表达式 为button添加ActionListener */ public void bindEventByLambda(){ button.addActionListener(e -> System.out.println("你好!")); }}

(5) Lambda表达式的使用场景

先说一个名词的概念——函数式接口(Functional Interface):

定义的一个接口,接口里面必须 有且只有一个抽象方法 ,这样的接口就成为函数式接口

在可以使用lambda表达式的地方,方法声明时必须包含一个函数式的接口。任何函数式接口都可以使用lambda表达式替换。例如:ActionListener、Comparator、Runnable

lambda表达式只能出现在目标类型为函数式接口的上下文中。如果我们提供的这个接口包含一个以上的Abstract Method,那么使用lambda表达式则会报错。

使用场景

这种场景其实很常见:你在某处就真的只需要一个能做一件事情的函数而已,连它叫什么名字都无关紧要。Lambda 表达式就可以用来做这件事。

顺带说一下它的优缺点

优点:1.极大的简化代码。去除了很多无用的Java代码,使得代码更为简洁明了。2.比匿名内部类更加高效(不确定)。  (编译器会生成专门的lambda方法,可以使用javap -p查看编译过后的代码)缺点:1.可读性差。在代码简洁的情况下,另一方面又让大多程序员很难读懂。  因为很少程序员接触使用它。(不过这个缺点不是本身缺点,而且源于程序员较少使用)

转载地址:https://bwshen.blog.csdn.net/article/details/82256178 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Java-JDBC技术
下一篇:深入理解密码学技术

发表评论

最新留言

第一次来,支持一个
[***.219.124.196]2024年04月17日 11时14分18秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章