这篇 Java 基础,我吹不动了(四)

简介: Hey guys,这里是程序员cxuan,欢迎你收看我最新一期的文章,这篇文章我补充了一些关于《Java基础核心总结》的内容,修改了部分错别字和语句不通顺的地方,并且对内部类、泛型等内容进行了一定的补充,并且我在文章有些地方给出了一些链接,这些链接都是我自己写的硬核文章,能够更好的帮助你理解 Java 这门语言,那么废话不多说,下面开始正文。

内部类

距今为止,我们了解的都是普通类的定义,那就是直接在 IDEA 中直接新建一个 class 。

微信图片_20220417151038.jpg

新建完成后,你就会拥有一个 class 文件的定义,这种操作太简单了,时间长了就会枯燥,我们年轻人多需要更新潮和骚气的写法,好吧,既然你提到了那就使用 内部类吧,这是一种有用而且骚气的定义类的方式,内部类的定义非常简单:可以将一个类的定义放在另一个类的内部,这就是内部类

内部类是一种非常有用的特性,定义在类内部的类,持有外部类的引用,但却对其他外部类不可见,看起来就像是一种隐藏代码的机制,就和 弗兰奇将军 似的,弗兰奇可以和弗兰奇将军进行通讯,但是外面的敌人却无法直接攻击到弗兰奇本体。

微信图片_20220417151045.jpg

下面我们就来聊一聊创建内部类的方式。

如何定义内部类

下面是一种最简单的内部类定义方式:

public class Parcel1 {
    public class Contents{
        private int value = 0;
        public int getValue(){
            return value;
        }
    }
}

这是一个很简单的内部类定义方式,你可以直接把一个类至于另一个类的内部,这种定义 Contents 类的方式被称为内部类。

那么,就像上面代码所展示的,程序员该如何访问 Contents 中的内容呢?

public class Parcel1 {
    public class Contents{
        private int value = 0;
        public int getValue(){
            return value;
        }
    }
    public Contents contents(){
        return new Contents();
    }
    public static void main(String[] args) {
        Parcel1 p1 = new Parcel1();
        Parcel1.Contents pc1 = p1.contents();
        System.out.println(pc1.getValue());
    }
}

就像上面代码看到的那样,你可以写一个方法来访问 Contents,相当于指向了一个对 Contents 的引用,可以用外部类.内部类这种定义方式来创建一个对于内部类的引用,就像 Parcel1.Contents pc1 = p1.contents() 所展示的,而 pc1 相当于持有了对于内部类 Contents 的访问权限。

现在,我就有一个疑问,如果上面代码中的 contents 方法变为静态方法,pc1 还能访问到吗?

编译就过不去,那么为什么会访问不到呢?请看接下来的分析。

链接到外部类

看到这里,你还不明白为什么要采用这种方式来编写代码,好像只是为了装 B ?或者你觉得重新定义一个类很麻烦,干脆直接定义一个内部类得了,好像到现在并没有看到这种定义内部类的方式为我们带来的好处。请看下面这个例子

public class Parcel2 {
    private static int i = 11;
    public class Parcel2Inner {
        public Parcel2Inner(){
            i++;
        }
        public int getValue(){
            return i;
        }
    }
    public Parcel2Inner parcel2Inner(){
        return new Parcel2Inner();
    }
    public static void main(String[] args) {
        Parcel2 p2 = new Parcel2();
        for(int i = 0;i < 5;i++){
            p2.parcel2Inner();
        }
        System.out.println("p2.i = " + p2.i);
    }
}

输出结果:16

当你创建了一个内部类对象的时候,此对象就与它的外围对象产生了某种联系,如上面代码所示,内部类Parcel2Inner 是可以访问到 Parcel2 中的 i 的值,也可以对这个值进行修改。

那么,问题来了,如何创建一个内部类的对象呢?程序员不能每次都写一个方法返回外部类的对象吧?看下面代码:

public class Parcel3 {
    public class Contents {
        public Parcel3 dotThis(){
            return Parcel3.this;
        }
        public String toString(){
            return "Contents";
        }
    }
    public Parcel3 contents(){
        return new Contents().dotThis();
    }
    public String toString(){
        return "Parcel3";
    }
    public static void main(String[] args) {
        Parcel3 pc3 = new Parcel3();
        Contents c = pc3.new Contents();
        Parcel3 parcel3 = pc3.contents();
        System.out.println(pc3);
        System.out.println(c);
        System.out.println(parcel3);
    }
}

输出: Parcel3 Contents Parcel3

如上面代码所示,Parcel3 内定义了一个内部类 Contents,内部类中定义了一个方法 dotThis(),这个方法的返回值为外部类的对象,在外部类中有一个 contents() 方法,这个方法返回的还是外部类的引用。

内部类与向上转型

本文到现在所展示的都是本类持有内部类的访问权限,那么,与此类无关的类是如何持有此类内部类的访问权限呢?而且内部类与向上转型到底有什么关系呢?

public interface Animal {
    void eat();
}
public class Parcel4 {
    private class Dog implements Animal {
        @Override
        public void eat() {
            System.out.println("啃骨头");
        }
    }
    public Animal getDog(){
        return new Dog();
    }
    public static void main(String[] args) {
        Parcel4 p4 = new Parcel4();
        //Animal dog = p4.new Dog();
        Animal dog = p4.getDog();
        dog.eat();
    }
}

输出:啃骨头

这个输出大家肯定都知道了,Dog 是由 private 修饰的,按说非本类的任何一个类都是访问不到,那么为什么能够访问到呢?仔细想一下便知,因为 Parcel4 是 public 的,而 Parcel4 是可以访问自己的内部类的,那么 Animal 也可以访问到 Parcel4 的内部类也就是 Dog 类,并且 Dog 类是实现了 Animal 接口,所以 getDog() 方法返回的也是 Animal 类的子类,从而达到了向上转型的目的,让代码更美妙。

定义在方法中和任意作用域内部的类

上面所展示的一些内部类的定义都是普通内部类的定义,如果我想在一个方法中或者某个作用域内定义一个内部类该如何编写呢?

你可能会考虑这几种定义的思路:

  1. 我想定义一个内部类,它实现了某个接口,我定义内部类是为了返回接口的引用
  2. 我想解决某个问题,并且这个类又不希望它是公共可用的,顾名思义就是封装起来,不让别人用
  3. 因为懒...

以下是几种定义内部类的方式:

  • 一个在方法中定义的类(局部内部类)
  • 一个定义在作用域内的类,这个作用域在方法的内部(成员内部类)
  • 一个实现了接口的匿名类(匿名内部类)
  • 一个匿名类,它扩展了非默认构造器的类
  • 一个匿名类,执行字段初始化操作
  • 一个匿名类,它通过实例初始化实现构造
  • 定义在方法内部的类又被称为局部内部类
public class Parcel5 {
  private Destination destination(String s){
    class PDestination implements Destination{
      String label;
      public PDestination(String whereTo){
        label = whereTo;
      }
      @Override
      public String readLabel() {
        return label;
      }
    }
    return new PDestination(s);
  }
  public static void main(String[] args) {
    Parcel5 p5 = new Parcel5();
    Destination destination = p5.destination("China");
    System.out.println(destination.readLabel());
  }
}

输出 :China

如上面代码所示,你可以在编写一个方法的时候,在方法中插入一个类的定义,而内部类中的属性是归类所有的,我在写这段代码的时候很好奇,内部类的执行过程是怎样的,Debugger走了一下发现当执行到p5.destination("China") 的时候,先会执行 return new PDestination(s),然后才会走 PDestination 的初始化操作,这与我们对其外部类的初始化方式是一样的,只不过这个方法提供了一个访问内部类的入口而已。

局部内部类的定义不能有访问修饰符

  • 一个定义在作用域内的类,这个作用域在方法的内部
public class Parcel6 {
  // 吃椰子的方法
  private void eatCoconut(boolean flag){
    // 如果可以吃椰子的话
    if(flag){
      class Coconut {
        private String pipe;
        public Coconut(String pipe){
          this.pipe = pipe;
        }
        // 喝椰子汁的方法
        String drinkCoconutJuice(){
          System.out.println("喝椰子汁");
          return pipe;
        }
      }
      // 提供一个吸管,可以喝椰子汁
      Coconut coconut = new Coconut("用吸管喝");
      coconut.drinkCoconutJuice();
    }
    /**
             * 如果可以吃椰子的话,你才可以用吸管喝椰子汁
             * 如果不能接到喝椰子汁的指令的话,那么你就不能喝椰子汁
             */
    // Coconut coconut = new Coconut("用吸管喝");
    // coconut.drinkCoconutJuice();
  }
  public static void main(String[] args) {
    Parcel6 p6 = new Parcel6();
    p6.eatCoconut(true);
  }
}

输出:喝椰子汁

如上面代码所示,只有程序员告诉程序,现在我想吃一个椰子,当程序接收到这条命令的时候,它回答好的,马上为您准备一个椰子,并提供一个吸管让您可以喝到新鲜的椰子汁。程序员如果不想吃椰子的话,那么程序就不会为你准备椰子,更别说让你喝椰子汁了。

  • 一个实现了匿名接口的类

我们都知道接口是不能被实例化的,也就是说你不能 return 一个接口的对象,你只能是返回这个接口子类的对象,但是如果像下面这样定义,你会不会表示怀疑呢?

public interface Contents {
    int getValue();
}
public class Parcel7 {
    private Contents contents(){
        return new Contents() {
            private int value = 11;
            @Override
            public int getValue() {
                return value;
            }
        };
    }
    public static void main(String[] args) {
        Parcel7 p7 = new Parcel7();
        System.out.println(p7.contents().getValue());
    }
}

输出 : 11

为什么能够返回一个接口的定义?而且还有 {},这到底是什么鬼?这其实是一种匿名内部类的写法,其实和上面所讲的内部类和向上转型是相似的。也就是说匿名内部类返回的 new Contents() 其实也是属于 Contents 的一个实现类,只不过这个实现类的名字被隐藏掉了,能用如下的代码示例来进行转换:

public class Parcel7b {
    private class MyContents implements Contents {
        private int value = 11;
        @Override
        public int getValue() {
            return 11;
        }
    }
    public Contents contents(){
        return new MyContents();
    }
    public static void main(String[] args) {
        Parcel7b parcel7b = new Parcel7b();
        System.out.println(parcel7b.contents().getValue());
    }
}

输出的结果你应该知道了吧~!你是不是觉得这段代码和 10.3 章节所表示的代码很一致呢?

  • 一个匿名类,它扩展了非默认构造器的类

如果你想返回一个带有参数的构造器(非默认的构造器),该怎么表示呢?

public class WithArgsConstructor {
    private int sum;
    public WithArgsConstructor(int sum){
        this.sum = sum;
    }
    public int sumAll(){
        return sum;
    }
}
public class Parcel8 {
    private WithArgsConstructor withArgsConstructor(int x){
        // 返回WithArgsConstructor带参数的构造器,执行字段初始化
        return new WithArgsConstructor(x){
            // 重写sumAll方法,实现子类的执行逻辑
            @Override
            public int sumAll(){
                return super.sumAll() * 2;
            }
        };
    }
    public static void main(String[] args) {
        Parcel8 p8 = new Parcel8();
        System.out.println(p8.withArgsConstructor(10).sumAll());
    }
}

以上 WithArgsConstructor 中的代码很简单,定义一个 sum 字段,构造器进行初始化,sumAll 方法返回 sum 的值,Parcel8 中的 withArgsConstructor 方法直接返回 x 的值,但是在这个时候,你想在返回值上做一些特殊的处理,比如你想定义一个类,重写 sumAll 方法,来实现子类的业务逻辑。Java 编程思想198页中说 代码中的“;”并不是表示内部类结束,而是表达式的结束,只不过这个表达式正巧包含了匿名内部类而已。

  • 一个匿名类,它能够执行字段初始化

上面代码确实可以进行初始化操作,不过是通过构造器执行字段的初始化,如果没有带参数的构造器,还能执行初始化操作吗?这样也是可以的。

public class Parcel9 {
    private Destination destination(String dest){
        return new Destination() {
            // 初始化赋值操作
            private String label = dest;
            @Override
            public String readLabel() {
                return label;
            }
        };
    }
    public static void main(String[] args) {
        Parcel9 p9 = new Parcel9();
        System.out.println(p9.destination("pen").readLabel());
    }
}

Java 编程思想 p198 中说如果给字段进行初始化操作,那么形参必须是 final 的,如果不是 final,编译器会报错,这部分提出来质疑,因为我不定义为final,编译器也没有报错。我考虑过是不是 private 的问题,当我把 private 改为 public,也没有任何问题。

我不清楚是中文版作者翻译有问题,还是经过这么多 Java 版本的升级排除了这个问题,我没有考证原版是怎样写的,这里还希望有知道的大牛帮忙解释一下这个问题。

  • 一个匿名类,它通过实例初始化实现构造
public abstract class Base {
    public Base(int i){
        System.out.println("Base Constructor = " + i);
    }
    abstract void f();
}
public class AnonymousConstructor {
    private static Base getBase(int i){
        return new Base(i){
            {
                System.out.println("Base Initialization" + i);
            }
            @Override
            public void f(){
                System.out.println("AnonymousConstructor.f()方法被调用了");
            }
        };
    }
    public static void main(String[] args) {
        Base base = getBase(57);
        base.f();
    }
}

输出:Base Constructor = 57 Base Initialization 57 AnonymousConstructor.f()方法被调用了

这段代码和 "一个匿名类,它扩展了非默认构造器的类" 中属于相同的范畴,都是通过构造器实现初始化的过程。

嵌套类

上面我们介绍了 6 种内部类定义的方式,现在我们来解决一下刚开始提出的疑问,为什么 contents() 方法变成静态的,会编译出错的原因:

Java编程思想 p201 页讲到:如果不需要内部类与其外围类之前产生关系的话,就把内部类声明为 static。这通常称为嵌套类,也就是说嵌套类的内部类与其外围类之前不会产生某种联系,也就是说内部类虽然定义在外围类中,但是确实可以独立存在的。嵌套类也被称为静态内部类。

静态内部类意味着:

  1. 要创建嵌套类的对象,并不需要其外围类的对象
  2. 不能从嵌套类的对象中访问非静态的外围类对象

看下面代码

public class Parcel10 {
    private int value = 11;
    static int bValue = 12;
    // 静态内部类
    private static class PContents implements Contents {
        // 编译报错,静态内部类PContents中没有叫value的字段
        @Override
        public int getValue() {
            return value;
        }
        // 编译不报错,静态内部类PContents可以访问静态属性bValue
        public int f(){
            return bValue;
        }
    }
    // 普通内部类
    private class PDestination implements Destination {
        @Override
        public String readLabel() {
            return "label";
        }
    }
    // 编译不报错,因为静态方法可以访问静态内部类
    public static Contents contents(){
        return new PContents();
    }
    // 编译报错,因为非静态方法不能访问静态内部类
    public Contents contents2(){
        Parcel10 p10 = new Parcel10();
        return p10.new PContents();
    }
    // 编译不报错,静态方法可以访问非静态内部类
    public static Destination destination(){
        Parcel10 p10 = new Parcel10();
        return p10.new PDestination();
    }
    // 编译不报错,非静态方法可以访问非静态内部类
    public Destination destination2(){
        return new PDestination();
    }
}

由上面代码可以解释,编译出错的原因是静态方法不能直接访问非静态内部类,而需要通过创建外围类的对象来访问普通内部类

接口内部的类

纳尼?接口内部只能定义方法,难道接口内部还能放一个类吗?可以!

正常情况下,不能在接口内部放置任何代码,但是嵌套类作为接口的一部分,你放在接口中的任何类默认都是public 和 static 的。因为类是 static 的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则,你甚至可以在内部类实现外部类的接口,不过一般我们不提倡这么写。

public interface InnerInterface {
    void f();
    class InnerClass implements InnerInterface {
        @Override
        public void f() {
            System.out.println("实现了接口的方法");
        }
        public static void main(String[] args) {
            new InnerClass().f();
        }
    }
    // 不能在接口中使用main方法,你必须把它定义在接口的内部类中
//    public static void main(String[] args) {}
}

输出:实现了接口的方法

内部类实现多重继承

在 Java 中,类与类之间的关系通常是一对一的,也就是单项继承原则,那么在接口中,类与接口之间的关系是一对多的,也就是说一个类可以实现多个接口,而接口和内部类结合可以实现"多重继承",并不是说用 extends 关键字来实现,而是接口和内部类的对多重继承的模拟实现。

参考 chenssy 的文章 http://www.cnblogs.com/chenssy/p/3389027.html 已经写的很不错了。

public class Food {
    private class InnerFruit implements Fruit{
        void meakFruit(){
            System.out.println("种一个水果");
        }
    }
    private class InnerMeat implements Meat{
        void makeMeat(){
            System.out.println("煮一块肉");
        }
    }
    public Fruit fruit(){
        return new InnerFruit();
    }
    public Meat meat(){
        return new InnerMeat();
    }
    public static void main(String[] args) {
        Food food = new Food();
        InnerFruit innerFruit = (InnerFruit)food.fruit();
        innerFruit.meakFruit();
        InnerMeat innerMeat = (InnerMeat) food.meat();
        innerMeat.makeMeat();
    }
}

输出:种一个水果 煮一块肉

内部类的继承

内部类之间也可以实现继承,与普通类之间的继承相似,不过不完全一样。

public class BaseClass {
    class BaseInnerClass {
        public void f(){
            System.out.println("BaseInnerClass.f()");
        }
    }
    private void g(){
        System.out.println("BaseClass.g()");
    }
}
/**
 *  可以看到,InheritInner只是继承自内部类BaseInnerClass,而不是外围类
 *  但是默认的构造方式会报编译错误,
 *  必须使用类似enclosingClassReference.super()才能编译通过
 *  用来来说明内部类与外部类对象引用之间的关联。
 *
 */
public class InheritInner extends BaseClass.BaseInnerClass{
    // 编译出错
//    public InheritInner(){}
    public InheritInner(BaseClass bc){
        bc.super();
    }
    @Override
    public void f() {
        System.out.println("InheritInner.f()");
    }
    /*
    * 加上@Override 会报错,因为BaseInnerClass 中没有g()方法
    * 这也是为什么覆写一定要加上Override注解的原因,否则默认是本类
    * 中持有的方法,会造成误解,程序员以为g()方法是重写过后的。
    @Override
    public void g(){
        System.out.println("InheritInner.g()");
    }*/
    public static void main(String[] args) {
        BaseClass baseClass = new BaseClass();
        InheritInner inheritInner = new InheritInner(baseClass);
        inheritInner.f();
    }
}

输出:InheritInner.f()

内部类的覆盖

关于内部类的覆盖先来看一段代码:

public class Man {
    private ManWithKnowledge man;
    protected class ManWithKnowledge {
        public void haveKnowledge(){
            System.out.println("当今社会是需要知识的");
        }
    }
    // 我们想让它输出子类的haveKnowledge()方法
    public Man(){
        System.out.println("当我们有了一个孩子,我们更希望他可以当一个科学家,而不是网红");
        new ManWithKnowledge().haveKnowledge();
    }
}
// 网红
public class InternetCelebrity extends Man {
    protected class ManWithKnowledge {
        public void haveKnowledge(){
            System.out.println("网红是当今社会的一种病态");
        }
    }
    public static void main(String[] args) {
        new InternetCelebrity();
    }
}

输出:当我们有了一个孩子,我们更希望他可以当一个科学家,而不是网红 当今社会是需要知识的

我们默认内部类是可以覆盖的。所以我们想让他输出 InternetCelebrity.haveKnowledge() , 来实现我们的猜想,但是却输出了 ManWithKnowledge.haveKnowledge() 方法。

这个例子说明当继承了某个外围类的时候,内部类并没有发生特别神奇的变化,两个内部类各自独立,都在各自的命名空间内。

关于源码中内部类的表示

由于每个类都会产生一个.class 文件,包含了创建该类型对象的全部信息。

同样的,内部类也会生成一个.class 文件,表示方法为: OneClass$OneInnerClass

内部类的优点

下面总结一下内部类的优点:

1、封装部分代码,当你创建一个内部类的时候,该内部类默认持有外部类的引用;

2、内部类具有一定的灵活性,无论外围类是否继承某个接口的实现,对于内部类都没有影响;

3、内部类能够有效的解决多重继承的问题。

集合

集合在我们的日常开发中所使用的次数简直太多了,你已经把它们都用的熟透了,但是作为一名合格的程序员,你不仅要了解它的基本用法,你还要了解它的源码;存在即合理,你还要了解它是如何设计和实现的,你还要了解它的衍生过程。

这篇博客就来详细介绍一下 Collection 这个庞大集合框架的家族体系和成员,让你了解它的设计与实现。

是时候祭出这张神图了

微信图片_20220417151107.jpg

首先来介绍的就是列表爷爷辈儿的接口- Iterator

Iterable 接口

实现此接口允许对象成为 for-each 循环的目标,也就是增强 for 循环,它是 Java 中的一种语法糖

List<Object> list = new ArrayList();
for (Object obj: list){}

除了实现此接口的对象外,数组也可以用 for-each 循环遍历,如下:

Object[] list = new Object[10];
for (Object obj: list){}

其他遍历方式

jdk 1.8之前Iterator只有 iterator 一个方法,就是

Iterator<T> iterator();

实现次接口的方法能够创建一个轻量级的迭代器,用于安全的遍历元素,移除元素,添加元素。这里面涉及到一个 fail-fast 机制。

总之一点就是能创建迭代器进行元素的添加和删除的话,就尽量使用迭代器进行添加和删除。

也可以使用迭代器的方式进行遍历

for(Iterator it = coll.iterator(); it.hasNext(); ){
    System.out.println(it.next());
}

顶层接口

Collection 是一个顶层接口,它主要用来定义集合的约定

List 接口也是一个顶层接口,它继承了 Collection 接口 ,同时也是 ArrayList、LinkedList 等集合元素的父类

Set 接口位于与 List 接口同级的层次上,它同时也继承了 Collection 接口。Set 接口提供了额外的规定。它对add、equals、hashCode  方法提供了额外的标准。

Queue 是和 List、Set 接口并列的 Collection 的三大接口之一。Queue 的设计用来在处理之前保持元素的访问次序。除了 Collection 基础的操作之外,队列提供了额外的插入,读取,检查操作。

SortedSet 接口直接继承于 Set 接口,使用 Comparable 对元素进行自然排序或者使用 Comparator 在创建时对元素提供定制的排序规则。set 的迭代器将按升序元素顺序遍历集合。

Map 是一个支持 key-value 存储的对象,Map 不能包含重复的 key,每个键最多映射一个值。这个接口代替了Dictionary 类,Dictionary 是一个抽象类而不是接口。

ArrayList

ArrayList 是实现了 List 接口的可扩容数组(动态数组),它的内部是基于数组实现的。它的具体定义如下:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {...}
  • ArrayList 可以实现所有可选择的列表操作,允许所有的元素,包括空值。ArrayList 还提供了内部存储 list 的方法,它能够完全替代 Vector,只有一点例外,ArrayList 不是线程安全的容器。
  • ArrayList 有一个容量的概念,这个数组的容量就是 List 用来存储元素的容量。
  • ArrayList 不是线程安全的容器,如果多个线程中至少有两个线程修改了 ArrayList 的结构的话就会导致线程安全问题,作为替代条件可以使用线程安全的 List,应使用 Collections.synchronizedList
List list = Collections.synchronizedList(new ArrayList(...))
  • ArrayList 具有 fail-fast 快速失败机制,能够对 ArrayList 作出失败检测。当在迭代集合的过程中该集合在结构上发生改变的时候,就有可能会发生 fail-fast,即抛出 ConcurrentModificationException异常。

Vector

Vector 同 ArrayList 一样,都是基于数组实现的,只不过 Vector 是一个线程安全的容器,它对内部的每个方法都简单粗暴的上锁,避免多线程引起的安全性问题,但是通常这种同步方式需要的开销比较大,因此,访问元素的效率要远远低于 ArrayList。

还有一点在于扩容上,ArrayList 扩容后的数组长度会增加 50%,而 Vector 的扩容长度后数组会增加一倍。

LinkedList 类

LinkedList 是一个双向链表,允许存储任何元素(包括 null )。它的主要特性如下:

  • LinkedList 所有的操作都可以表现为双向性的,索引到链表的操作将遍历从头到尾,视哪个距离近为遍历顺序。
  • 注意这个实现也不是线程安全的,如果多个线程并发访问链表,并且至少其中的一个线程修改了链表的结构,那么这个链表必须进行外部加锁。或者使用
List list = Collections.synchronizedList(new LinkedList(...))

Stack

堆栈是我们常说的后入先出(吃了吐)的容器 。它继承了 Vector 类,提供了通常用的 push 和 pop 操作,以及在栈顶的 peek 方法,测试 stack 是否为空的 empty 方法,和一个寻找与栈顶距离的 search 方法。

第一次创建栈,不包含任何元素。一个更完善,可靠性更强的 LIFO 栈操作由 Deque 接口和他的实现提供,应该优先使用这个类

Deque<Integer> stack = new ArrayDeque<Integer>()

HashSet

HashSet 是 Set 接口的实现类,由哈希表支持(实际上 HashSet 是 HashMap 的一个实例)。它不能保证集合的迭代顺序。这个类允许 null 元素。

  • 注意这个实现不是线程安全的。如果多线程并发访问 HashSet,并且至少一个线程修改了set,必须进行外部加锁。或者使用 Collections.synchronizedSet() 方法重写。
  • 这个实现支持 fail-fast 机制。

TreeSet

TreeSet 是一个基于 TreeMap 的 NavigableSet 实现。这些元素使用他们的自然排序或者在创建时提供的Comparator 进行排序,具体取决于使用的构造函数。

  • 此实现为基本操作 add,remove 和 contains 提供了 log(n) 的时间成本。
  • 注意这个实现不是线程安全的。如果多线程并发访问 TreeSet,并且至少一个线程修改了 set,必须进行外部加锁。或者使用
SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...))
  • 这个实现持有 fail-fast 机制。

LinkedHashSet 类

LinkedHashSet 继承于 Set,先来看一下 LinkedHashSet 的继承体系:

微信图片_20220417151115.jpg

LinkedHashSet 是 Set 接口的 Hash 表和 LinkedList 的实现。这个实现不同于 HashSet 的是它维护着一个贯穿所有条目的双向链表。此链表定义了元素插入集合的顺序。注意:如果元素重新插入,则插入顺序不会受到影响。

  • LinkedHashSet 有两个影响其构成的参数:初始容量和加载因子。它们的定义与 HashSet 完全相同。但请注意:对于 LinkedHashSet,选择过高的初始容量值的开销要比 HashSet 小,因为 LinkedHashSet 的迭代次数不受容量影响。
  • 注意 LinkedHashSet 也不是线程安全的,如果多线程同时访问 LinkedHashSet,必须加锁,或者通过使用
Collections.synchronizedSet
  • 该类也支持fail-fast机制

PriorityQueue

PriorityQueue 是 AbstractQueue 的实现类,优先级队列的元素根据自然排序或者通过在构造函数时期提供Comparator 来排序,具体根据构造器判断。PriorityQueue 不允许 null 元素。

  • 队列的头在某种意义上是指定顺序的最后一个元素。队列查找操作 poll,remove,peek 和 element 访问队列头部元素。
  • 优先级队列是无限制的,但具有内部 capacity,用于控制用于在队列中存储元素的数组大小。
  • 该类以及迭代器实现了 Collection、Iterator 接口的所有可选方法。这个迭代器提供了 iterator() 方法不能保证以任何特定顺序遍历优先级队列的元素。如果你需要有序遍历,考虑使用 Arrays.sort(pq.toArray())
  • 注意这个实现不是线程安全的,多线程不应该并发访问 PriorityQueue 实例如果有某个线程修改了队列的话,使用线程安全的类 PriorityBlockingQueue
相关文章
|
6月前
|
存储 缓存 Java
最新Java基础系列课程--Day10-IO流文件处理
最新Java基础系列课程--Day10-IO流文件处理
|
6月前
|
存储 Java
最新Java基础系列课程--Day10-IO流文件处理(一)
最新Java基础系列课程--Day10-IO流文件处理
|
1月前
|
存储 缓存 Java
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
这篇文章详细介绍了Java中的IO流,包括字符与字节的概念、编码格式、File类的使用、IO流的分类和原理,以及通过代码示例展示了各种流的应用,如节点流、处理流、缓存流、转换流、对象流和随机访问文件流。同时,还探讨了IDEA中设置项目编码格式的方法,以及如何处理序列化和反序列化问题。
67 1
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
|
5月前
|
Java
【Java基础】输入输出流(IO流)
Java基础、输入输出流、IO流、流的概念、输入输出流的类层次结构图、使用 InputStream 和 OutputStream流类、使用 Reader 和 Writer 流类
164 2
|
2月前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
5月前
|
安全 Java
|
5月前
|
搜索推荐 算法 Java
【Java基础】 几种简单的算法排序
几种简单的JAVA算法排序
51 4
|
5月前
|
存储 缓存 Java
Java基础17-读懂Java IO流和常见面试题(二)
Java基础17-读懂Java IO流和常见面试题(二)
41 0
|
5月前
|
存储 Java Unix
Java基础17-读懂Java IO流和常见面试题(一)
Java基础16-读懂Java IO流和常见面试题(一)
73 0
|
6月前
|
Java
Java基础教程(12)-Java中的IO流
【4月更文挑战第12天】Java IO涉及输入输出,包括从外部读取数据到内存(如文件、网络)和从内存输出到外部。流是信息传输的抽象,分为字节流和字符流。字节流处理二进制数据,如InputStream和OutputStream,而字符流处理Unicode字符,如Reader和Writer。File对象用于文件和目录操作,Path对象简化了路径处理。ZipInputStream和ZipOutputStream则用于读写zip文件。