JAVA 笔记(二)

从5V降到3.3V,你的LDO到底有多烫?手把手算功耗、选散热,避开这些坑 本文详细解析了LDO在5V转3.3V电路中的发热问题,提供了精确的功耗计算公式和散热设计方法。通过对比不同封装的热阻参数和优化PCB布局,帮助工程师有效降低LDO温度,避免热保护触发。文章还探讨了替代方案的技术拐点,为电源系统优化设计提供实用指导。 阅读详情

关于interface值得被了解的东西

当我们最初开始接触编程的时候,都会听到类似,你可以使用这个API,使用那个API 类似的说法。
没错,API就是说Application Programming Interface,也就是应用程序编程接口。
但对应到Java里来说,初学乍练之时可能会造成一定的疑惑。因为,大部分时间来说,
你发现在使用的不是别人的工具类(class)里的方法,但是Java里的接口,不是说用interface定义的吗?

要解开这个误区,我们先来看一看所谓的API到底指的是什么?书面定义来说:API是一些预先定义的函数,目的是提供应用程序与开发人员基于
某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。

那么就很好理解了。以Java来说,我们开发中离不开使用的就是JDK里的API。
对应来说,我们实际上通常确实就是在使用JDK里提供的一个个类(也就是class)当中的一个个方法(即函数)。
但同时值得注意的是,我们关心的重点在于,去使用它们来协助完成我们需要的功能,但并不会去操心其内部的具体实现。
所以,我们其实可以看到,这和对于API的概念定义是很相符的。

而我们来看一下接口的书面定义:接口泛指实体把自己提供给外界的一种抽象化物(可以为另一实体),
用以由内部操作分离出外部沟通方法,使其能被修改内部而不影响外界其他实体与其交互的方式。
那么,Java当中关键字interface的自身特性就恰好符合这个概念:只提供方法声明,但不提供具体实现。
但是,我们也可以想到interface声明的接口概念,更像是Java中带有专用型的一种产物。
因为通过接口的概念,我们也可以得知,远远不会仅仅如此。就如同下面的代码一样:

public class Demo {

    public void f(){
        innterMehotd();
    }

    // 隐藏的内部实现
    private void innterMehotd(){

    }
}

这段代码中暴露出的公有方法f()也可以被理解为传统意义上的程序接口。
因为我们可以通过改变innterMehotd()的实现细节来改变程序,但并不会影响f()的消费者。

到这里,我们已经不难理解了,以API和Java里的interface来说:
其实二者都符合对于接口的定义。如果说有所不同,可能就是API泛指了更为广义上的接口定义。
而接口的关键,简而概之就是:我提供访问程序的入口,但内部细节无需你关心。
这也是为什么我们后面会说到,针对接口编程能够降低代码耦合度。就是因为:
接口的特性让它能够保证,即使其实现细节被改变,但并不会影响到其消费者,因为你消费的仅仅只是程序入口。

想必到此,我们已经弄清楚了关于接口的概念上的东西,这很有帮助。
接下来,我们就要来好好看看interface这个东西,了解一下那些关于它值得被了解的有趣的细节和知识。

接口常见的使用方式

  • 接口被用来建立类与类之间的协议。

说到协议我们可能就很容易想到类似HTTP、TCP/IP这样的通信协议。没错,制定协议最大的目的就是:
针对于某项多个个体共同参与的事物,让其必须遵循统一的规范。

而协议的书面意思是指两个或两个以上实体为了开展某项活动,经过协商后双方达成的一致意见。
那么我们首先就来看一种,最符合对于“协议”这个名词的概念的解释的使用方式。

OK,举例来说。假设,我们来设计一套关于机车生产的“协议”:

public interface Vehical {

    void drive();

    void stop();

    void turns();
}

就像这里的代码一样,在实际的生产过程中,可能存在着众多不同厂商生产的各种类型的汽车。
但没关系,只要你想要生产汽车,那么请遵循我的接口(协议)!
你怎么样来生产自己认为满意的汽车无所谓,但必须让它至少具备行驶,制动,转弯等功能(这是协议规定的)。

  • 通过接口进行框架搭建

事实上,针对于接口的特性,肯定还有另一些妙用。首先可能就是,你是一个项目的架构师。
于是你通过项目的需求分析,来设计项目。也就是说,你了解这个项目中的各个功能,但并不需要你来为这些功能提供具体实现。
你只需要针对于这个功能设计好实现规范,最后让相关的人员去负责完成。

以一个最基本的例子来说明,现在你搭建的项目中有一个负责购物的类。于是你开始设计:

public interface Shopping {
  void buy();
  void sale();
}

最终,当项目搭建完毕后,负责消费功能模块的人员就根据该接口去完成具体的功能类的实现。

  • 利用接口实现“回调”

接着,我们可以看到另一种更常见的使用方式:即我们常说的“回调”。
关于回调其实与我们说到的上一种使用方式的原理很类似。因为简单来说:
回调就是针对于某一问题(事物),你需要一个“解决方案”,但该方案可能存在多种方式或者你希望由别人来给出。

这样的表达,我不知道是不是准确。我们仍然通过一个例子,可以更好的进行理解。
在Android编程当中,我们经常会用到下面这样的代码:

button.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick() {
                System.out.println("用户点击按钮了");

            }
        });

这里的View.OnClickListener就是声明于View类当中的一个接口。我们可以思考一下为什么会设计有这样一个接口?
其实很简单,不正是因为android的设计者在设计View类的时候,就可以预见到View通常是具有“可点击性”的。
而当控件被点击时,对于在Android客户端的消费者来说,很自然的就需要得到一个“响应事件”。
但是这个响应事件既然位于客户端,就当然不会是android的设计者在设计时能够明确的。而应当由客户端的开发者根据不同的实际需求去提供的。

所以,没关系,我们就提供一个来处理“响应事件”的接口(规范)。
作为开发者,只需要按照规范去实现,我们就能按你所愿去回调并正确的响应你的事件。
所以我们不用去查看源码也不难想象,View类当中的设计思想肯定是类似于下面这样的:

public class View {

    private OnClickListener mOnClickListener;

    // 用户点击屏幕就触发该方法
    public void onClick() {
        // 回调接口,处理点击事件
        mOnClickListener.onClick();
    }

    public void setOnClickListener(OnClickListener listener) {
        mOnClickListener = listener;
    }

    public interface OnClickListener {
        void onClick();
    }
}

这实际上也是策略模式的一种体现,正如上面的例子中体现的:
我们的某个方法中接受的参数是某一个接口,于是使用者就可以根据自己的需求来实现这个接口;
然后将接口发送给我们提供的方法,从而得到不同的处理策略。

这时,我们再对我们上述的几种接口的使用方式进行总结。然后不难发现:
无论是哪种使用方式,其核心的思想任然源于接口最重要的特点:提供程序访问入口,不关心具体实现。

我们可以看到,在上面的各种例子中,我们所做的工作都是:

  • 在接口中定义好那些潜在的消费者需要访问的功能入口;
  • 但是该接口的具体细节,则由具体的对象根据实际情况去更好的实现;

对应于上面例子中的具体情况来说,就正如同:

  • “汽车生产协议”中的接口,是提供给那些使用该协议生产汽车的厂商。
  • “项目架构师”中的接口,是提供给负责实现该功能模块的核心程序员。
  • “Android当中的事件监听回调”中,接口是提供给负责具体事件响应实现的安卓客户端程序员。

  • 而实际上到了这里,根据我们上面说到的,我们不难总结到一个观点:接口的特性,让它特别适用于“合作“。
    为什么这样说,因为编程的工作基本不可能是一个人去完成的,接口的出现,使得我们可以做到:
    针对于那些“由他人负责编写的,但却在自己负责的部分可能需要使用到的功能”进行抽离,将这些部分抽离成接口。
    大家在自己的代码中针对于该接口编程(就像上面回调的例子中做的那样),在实现合作的同时,却又互不影响。


“interface”小知识

上面,我们说了一些interface常见的使用方式。下面我们来关注一些关于interface自身的小细节。

  • 默认的static final 域

interface里所有域都被自动的隐式的申明为static且final的,也就是说即时你不这么做,系统也会默认的为你添加,正如下面这样:

public interface Test{
    int A = 10; // ==  public static final int A = 10;
}

这是接口自身的规范,但我们要做的,是思考为什么有这样的设计规范。

首先,我们来分析一下被默认的声明为static是为什么?

很简单,我们知道Java当中interface和abstract class是不能被用以构造对象的。
那么,如果接口中的域不是静态而是属于成员的,这显然是自相矛盾,不合理的。

接着,我们已经说过了,接口的出现,就是为了建立类与类之间的协议。
也就是说,它出现的目的就为了定义多个类共同约定,同意遵循的规范。
那么如果其不是final的,也就证明它可以被任何实现了该接口的类所改变,也就达不到“统一规范”的目的了。

以我们上面说到的定义的“机车生产”的例子来说,假设我们在接口中定义了一个字段”public int tyre = 4;”。
则说明协议的制定者希望:遵循该规范生产的机车,轮胎的数量都按照4个的标准进行生产。

那么,针对于我们说分析的结果来说,就对应于:

  • 如果该字段不是static的,则每个实现了该接口的类都可以根据构造对象来改变轮胎数量。
  • 如果该字段不是final的,虽然每个实现了该接口的类仍然都会得到统一的轮胎数量。
    但这个轮胎数可以被任一的修改,于是可能会得到被修改为“5个”、“3个”等等这样的数量。

  • **默认的public abstract 方法**

闲话就不多说了,同域的声明一样,接口中的方法会自动的被声明为public abstarct。我们要研究的,仍然是为什么这样设计。

被默认设计为abstarct并不难理解,因为我们说了接口中的方法只声明规范,不提供具体实现。

而为什么是public就很有意思了。网上有一种说法是这样的,之所以这样设计是因为:
接口本身就是要被类所实现才有意义,所以如果它的方法不公有化,就没有意义了。

我个人是这样理解的,这种说法本身是有道理,但却不完全准确。为什么呢?
因为interface也和class一样,它也可以被定义为“包内私有”。也就是说,也可能存在所谓的“包内接口”。

那么想象这样一种情况:针对于包内的接口,肯定只有定义在同一包目录的类能够去实现该接口。
而如果我们将实现接口的类中的方法同样全部定义为包访问权限;也就是说,该接口的适用范围被严格控制在同一包目录下。
从而达到的效果是:类里从接口中实现而来的方法与接口中定义的方法,同样是只能够被同一包中的其它类访问。
那么,接口内的方法如果使用“包权限”访问修饰符,很明显也是行的通的。

但实际上,将接口的使用权限仅仅控制在同一包下,这和接口被设计出来的使用目的显然是相悖的。
因为位于同一包目录下的类关联已经足够密切,通常它们都是由同一个人或者同属的一群人来负责编写的。
而实际上,就像我们在之前说到的“Android中事件监听响应事件”中用到的接口一样。
该接口的出现,是Android系统的设计者们提供给那些,Android客户端程序员在将来使用的。
那么将接口的使用范围仅仅控制在包权限之中,显然就是远远不够的。所以将接口内的方法默认为public就很合理了。

这样的原理实际上很显而易见,但我们仍然通过一个最基础的例子来更好的进行理解:
首先,我们在包目录“a”里,定义了一个interface:

package com.tsr.a;

public interface Interface {

    void f();

}

接着,我们定义了一个类来实现该接口:

package com.tsr.a;

public class ClassA implements Interface{

    @Override
    public void f() {
        System.out.println("fffff");
    }

}

下面就是关键了,假设我们在包目录B下的一个类里针对该接口进行了编程:

package com.tsr.b;

import com.tsr.a.ClassA;
import com.tsr.a.Interface;

public class Test {

    public static void main(String[] args) {
        Test test = new Test();
        test.useInterface(new ClassA());

    }

    void useInterface(Interface in){
        in.f(); //想象一下,如果方法不为public,这里能访问到吗?
    }
}

结果就正如我们在程序注释里说的,如果接口的方法不是默认的被强制为public。
那么可能这个方法根本就很难被得以使用,从而当然也就失去了接口的意义。

Java提供权限访问修饰符,是出于面向对象的“封装性”的考虑。
因为对于类来说,我们应该选择去隐藏那些我们不希望提供给别人的部分。

但对于接口来说,其出现的本来目的就是对使用该接口的类声明用于声明一种公有的规范。
那么,在公有的规范里,却又希望一些东西不能被规范的遵守者察觉或使用,那么不是很矛盾吗?


针对于接口进行解耦

就正如我们在Java笔记 (一)当中说过,可以通过继承体系来对程序进行解耦一样。针对于接口,很多时候能够做到更大程度上的解耦。

这是因为Java对于“继承”的使用思想以及“单继承”的限制造成的。想象一下:

假设我们在程序中的某个方法,使用针对于某一个继承体系的基类进行代码编写的方式。
这样做很好,因为我们很好的降低了,位于这个继承体系当中的所有类之间的耦合关系。

但是,也可能存在这样一种情况:有一个位于该继承体系之外的类,它的方法结构完全能够适用于我们定义的方法,但却不能够被该方法接受。
我们当然可以强行将该类也改用继承,唯一的缺点只是这样做不符合继承”is-a”的思想罢了。
但还有一种更更糟的情况,那就是:如果此类本身就已经继承了某个类了,那你就别无他法了。

这说明我们的解耦工作还不彻底,就如下面的例子演示的一样:

public class Test {
    public static void main(String[] args) {
      test(new Father());
      test(new Son_1());
      test(new Son_2());
      test(new Son_3());
      test(new A()); // 不允许这样做,因为A位于Father继承体系之外
    }

    static void test(Father f){
        f.f1();
        f.f2();
    }
}

class Father {
    void f1() {}

    void f2() {}
}

class Son_1 extends Father {}

class Son_2 extends Father {}

class Son_3 extends Father {}

class A {

    void f1() {}

    void f2() {}
}

这个时候如果修改为针对于接口进行编程,就有了不同:

public class Test {
    public static void main(String[] args) {
        test(new Father());
        test(new Son_1());
        test(new Son_2());
        test(new Son_3());
        test(new A()); // 现在允许这样做了
    }

    static void test(I i) {
        i.f1();
        i.f2();
    }
}

interface I {
    void f1();

    void f2();
}

class Father implements I {
    public void f1() {}

    public void f2() {}
}

class Son_1 extends Father {}

class Son_2 extends Father {}

class Son_3 extends Father {}

class A implements I {

    public void f1() {}

    public void f2() {}
}

这样,通过将方法test()改为针对于接口编程,我们又进一步的降低了程序的耦合性。

那么,再一进步思考这样一种情况:假设class A不是你能够修改的类,也就是说它可能来自于某个类库。
这个时候,我们应该怎么做才能实现上面的解耦效果呢?也没关系,可以使用适配器模式。

public class Test {
    public static void main(String[] args) {
        test(new Father());
        test(new Son_1());
        test(new Son_2());
        test(new Son_3());
        test(new ClassAdapter(new A())); // 现在允许这样做了
    }

    static void test(I i) {
        i.f1();
        i.f2();
    }
}

interface I {
    void f1();

    void f2();
}

class Father implements I {
    public void f1() {}

    public void f2() {}
}

class Son_1 extends Father {}

class Son_2 extends Father {}

class Son_3 extends Father {}

class ClassAdapter implements I {

    private A a;

    ClassAdapter(A a){
        this.a = a;
    }

    public void f1() {
        a.f1();
    }

    public void f2() {
        a.f2();
    }
}

class A{
    void f1(){};

    void f2(){};
}


内部类的小知识

内部类是指那些嵌套在另一个类之中的类,它最大的长处就在于:
允许你把一系列逻辑相关的类组合在一起,而且可以根据需要控制它们的可见性。

一个常见的内部类的定义,简单得就像下面这样:

package com.tsr.demo;

public class TestClass {

    class Inner{ //内部类

    }
}

所以关于内部类的定义方式,确实没有什么值得好说的。我们来看一些有趣的东西。

关于内部类的对象创建

关于对于内部类的对象创建,有如下几种方式:

public class Outer {

    class Inner { // 内部类

    }

    void f() {
        Inner i = new Inner();
    }

    public static void main(String[] args) {
        //Inner i = new Inner();  不允许这样做

        //第一种方式
        Outer out = new Outer();
        Outer.Inner i1 = out.new Inner();
        // 第二种方式
        Outer.Inner i2 = new Outer().new Inner();
    }
}

从上面的代码中,我们不难发现,内部类对象的创建分为两种情况:

  • 在外部类的实例方法中,进行对象的创建,这个时候可以和创建普通类的方式一模一样。
  • 而在类的非静态方法之外的地方,想要创建内部类的对象,则必须保证先让其所属外部类的对象得到创建。

而造成这种现象的原因是因为,内部类的对象是依赖于其所属外部类对象而存在的。
当某个外部类对象创建了一个内部类的对象之后,该内部类的对象就会自动的捕获一个指向该外部类对象的引用。

内部类的妙用

  • 访问外部类的私有成员

根据上面我们说到的,内部类对象持有一个创建它的外部类对象的引用。由此我们也就不难想到一个特点:
那就是内部类将能够访问外部类的所有成员,甚至包括私有的成员。这是不是很牛逼呢?

我们可以针对于内部类的这一特征,来针对某一些现实问题进行巧妙的处理呢?想象一下这样一种情况:

我们成立了一家公司,公司自然有财务部门来负责处理公司的一些财务状况。对应于面向对象的思想来说,转化为代码的表现形式可能类似于:

public class MyCompany {
    private int money = 1000;
    private FinanceDepartment fDepartment;
}

public class FinanceDepartment {

    void doSomething() {}
}

问题在这个时候出现,财务部门的成员需要针对于“money”来进行工作。
简单的说就是:财务部门类必须能够拥有访问和修改这个变量的权利。
但是,公司的财务信息是很敏感和隐秘的,所以“money”字段出于隐私性的考虑,被申明为private。
于是财务部门无法访问到这个信息,也许你会说,我们可以通过提供公有的”get/set”方法来解决这个问题。
但这样做带来的隐患也显而易见,那就是任何其他类型都可以对该字段进行操作了。

这个时候,使用内部类就可以很好的解决这一问题:

public class MyCompany {
    private int money = 1000;

    class FinanceDepartment {

        void doSomething() {
              money = 2000;
        }
    }
}

这就像是公司头子对财务部门进行的公告:虽然“money”是隐秘的,但是没关系,咱们是内部同志的干活,我赋予你这个权利。

  • .this 的使用

回忆一下,当在继承中遇到了父类与子类中存在相同命名的域的时候,怎么来区分访问它们。是关键字”super”和”this”。

那么,想象一下如果在外部类和内部类存在相同命名的域的时候,又该怎么样区分呢?
没错,答案正是使用“.this”来进行访问,就像我们下面所做的这样:

public class Outer {

    int x = 10;

    class Inner {
        int x = 20;

        void f() {
            System.out.println(Outer.this.x); // 10
            System.out.println(x); // 20
            System.out.println(this.x); // 20
        }
    }
}

其实,原理也很简单,我们说了内部类会自动的持有一个创建它的外部类对象的引用。
而事实上,该引用也正是通过以”外部类名.this”这样的方式而持有的。

而在内部类自身的引用仍然是通过“this”来持有的,所以说:
上面的代码中使用x与this.x都没有区别,只是一种隐式和显示的表现形式的区别而已。

  • 通过内部类隐藏实现细节

现在我们来考虑这样一种情况,仍然以我们上面用到的公司与财务部门的例子来说:
对于内部类FinanceDepartment而言,其实它仍然可能暴露给其它很多成员。
因为它被定义为了包目录访问权限,所以这个时候,位于同一包内的其它任一类都是可以访问到它的。
但很多时候,我们希望类的实现细节是足够隐蔽的,通过内部类可以实现这一目的。

我们知道通常一个类只能被声明为两种访问权限:即公有的或者默认的包访问权限,
但内部类比较特别,Java的四种权限访问修饰符,它都能够使用。
结合于这一特质,再加上对于接口的使用,能够让我们最大限度的隐藏掉那些我们不希望暴露给别人的实现细节。

举例来说,我们有一个函数“doSomething”将要提供给消费者,同时但我们希望其实现细节能够最大程度的得以隐藏:

public class Outer {

    private class Inner implements I {

        @Override
        public void doSomething() {

        }

    }

    public I getInner() {
        return new Inner();
    }
}

interface I {
    void doSomething();
}

在这里,我们将私有的内部类配合接口进行了搭配。它带来的好处显而易见:

  • 消费者仍然可以访问到doSomething方法,只是变成了通过调用getInner().doSomething()的方式。
  • 而好处在于,消费者此时却无法得知任何关于Inner类的实现细节,因为它甚至无法了解到有这样一个内部类的存在。
  • 同时因为消费者得到的只是一个接口“I”类型的引用,由此也可以阻止其创建任何依赖于类型的编码。
    (因为他们别无选择,只能针对于该接口进行编程,从而降低代码的耦合度。)
  • 局部内部类的秘密

像我们之前说到的内部类,都是内部类最通常的表现形式。

而就像允许将类定义在类的内部一样,类也可以被定义在方法的作用域之中。通过这种方式定义的内部类,被称为局部内部类。
而被这样命名的原理,也同样不难理解。因为就和定义在方法中作用域内的变量被称为局部变量一样。

public class Outer {

    int num = 10;

    public int f() {
        // 局部内部类
        class Inner {
            int x = 20;

            int getSum() {
                return num + x;
            }
        }

        return new Inner().getSum();
    }
}

上面的例子展示了一个局部内部类的基本表现形式,并且我们也可以从中观察到局部内部类值得被留意的一些特点:

  1. 与其它的局部成员一样,局部类的有效范围被限定在包含的代码块中,一旦超出该范围,该局部内部类就不能被访问了。
  2. 局部内部类不能被访问修饰符修饰,也就是说不能使用private、protected、public任一修饰符。
    (因为作用域已经被限定在了当前所属的局部块中。)
  3. 普通的内部类虽然能访问任何其所属外部类的成员,但访问不到其所属外部类所定义的方法当中的局部变量。
    (这通常也是使用局部内部类的最常见原因)

而实际上,关于局部内部类我觉得其实有一个最值得弄清楚的问题,那就是:

  • 为什么在局部内部类当中使用到的参数必须被声明为final

我们首先来看一段熟悉的代码:

public class Test {
    void f(int x){
        new Thread(new Runnable() {

            @Override
            public void run() {
                int a = x +10;
            }
        }).start();
    }
}

上面的代码,相信每一个使用java的人都熟悉不过,但初学时我们都可能会碰到这样的问题,就是上面的代码编译不过。
必须将方法f()的参数x改为final修饰后,才能够编译通过。在很长一段的时间内,我对这个问题都弄不清楚,只知道照着规则去做就是了。

今天,我们就来分析一下,Java为什么要求我们这样做,我们先不说原理,先看一段代码:

public class Outer{

    public I f(){
        final int x = 10;
        class Inner implements I{

            @Override
            public void f1() {
                int a = x + 10;
            }

        }

        return new Inner();
    }

    interface I{
        void f1();
    }
}

相信看完上面的代码,机智的人已经能够猜到答案了。闲话少说,其实道理说透了也很简单,就像上例所演示的那样:

  • 首先,我们在方法f()当中定义了一个变量x。
  • 同时,我们在局部内部类当中使用到了这个变量。
  • 最后,方法()返回了一个局部内部类Inner的对象。

到此,我们不难想到,如果变量x不为final。那么可能出现的情况就是:

  • x在方法f()执行完毕后,随着栈帧出栈,也就是说从内存中清除了。
  • 但是方法f()却返回了一个局部内部类Inner对象,也就是说,在程序的其它地方,该局部内部类对象仍然可能会被使用。
  • 现在造成的尴尬局面就是:Inner对象仍然存在,仍然可能使用到变量x,但x却已经从内存中清除了。
  • 而将x用final修饰为什么能够解决这一问题?很简单,程序中的final域将并不存放在虚拟机栈中了。
    (final域将随着类被加载,被存放进方法区里的所属区域,自然也就避免了“出栈”而造成的内存丢失)。

  • 匿名内部类

其实,这个玩意个人看来并没什么太值得说的。但既然是在总结内部类。那就还是来看一看。
这东西相信不管了不了解匿名内部类这个名词,但实际上编写代码的过程中都写过N多次了。
因为Java里的多线程,经常就通过在这样的方式来创建:

public class Test{

    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub

            }
        });

        t.start();
    }
}

这里,对Tread类的构造器传入的参数new Runnable。。。。就是一个匿名内部类。
到此,其实也就不难想象,所谓的匿名内部类就和其名字一样,就是指一个没有“名字”的类。
因为我们也可以把上面的匿名内部类给他一个名字,就像下面这样:

public class Test {

    public static void main(String[] args) {
        class MyRunnable implements Runnable {

            @Override
            public void run() {
                // TODO Auto-generated method stub

            }

        }

        Thread t = new Thread(new MyRunnable());

        t.start();
    }
}

如你所见,现在当初的匿名内部类不再是“黑户”了,他有了自己的身份“MyRunnable”。

所以,对于匿名内部类,我们值得记住的可能主要就是以下两点:

  • 匿名内部类是对于内部类的一种简写形式。
  • 匿名内部类必须是基于继承自某个外部抽象类或者实现一个接口的基础上,才能实现。

  • 静态内部类

顾名思义,静态内部类就是指被static所修饰的内部类。就像下面这样:

public class Outer {

    static class Inner{

    }
}

与普通的内部类最大的不同就在于,静态内部类不会保存有一个指向其外部内对象的引用。
也就是说,与普通的内部类相比,它与外部类并没有那么密切的联系。
与其说是内部类,不如说它只是借助外部类寻找一个自己存在的区域。
所以它更像是嵌套在另一个类的当中的一个独立的类(唯一的不同可能就在于它可以访问外部类当中的私有的静态域)。
这也就是为什么,静态内部类又被称为“嵌套类”。

于是,关于静态内部类,我们可以归纳出以下特点:

  • 要创建静态内部类的对象,并不需要依赖其外部类对象。
  • 无法在静态内部类中,访问其外部类的非静态部分。
  • 普通的内部类无法包含static字段或方法,但静态内部类则可以。
  • 正对应于第三条,普通的内部类无法嵌套静态内部类;但静态内部类则可以包含普通的内部类。

  • 接口也可以嵌套静态内部类。

我们说过接口中的域默认都是static的,所以在接口内创建的内部类都将成为嵌套类。
而这样做的意义在于:假设你提供了一个接口,同时又希望为这个接口各个不同的实现者提供一些共有的方法。
那么,在接口中定义嵌套类来提供这些方法,就将是很好的选择。

  • 内部类的继承

虽然这种情况在实际开发中比较少见,但确实存在着内部类的继承。就像下面这样:

public class Outer {
     class Inner{

     }
}

class Another extends Outer.Inner{
    public Another(Outer o) {
        o.super();
    }
}

可以看到,就像在上面的例子里演示的一样。在继承一个内部类的时候,需要注意的是:

  • 声明继承的时候,要写出该内部类的全限定名(也就是OuterClassName.InnerClassName这样的形式)。
  • 对于实现继承的类的构造器,必须传入一个继承的内部类其所属外部类的对象引用。
  • 并且,在构造器中,还需要通过外部类引用.super()这样的方式先构造出继承的内部类所属外部类的对象。
    (这样做是因为:当使用继承的时候,构造子类对象之前,将先完成其父类对象的构造。同时,
    内部类又将隐式的持有其所属外部类的对象引用,所以还需要先秘密完成其外部类引用的初始化。)


  • 为什么使用内部类

我们前面已经总结了如此之多关于内部类的知识以及使用方式。其实,在实际使用中,我们最常在什么时候才会使用到内部类呢?

  • 当遇到“当前类当中需要的某个操作,应当放在另一个单独的特殊类中更为合适。因为要保持类足够的专用性;
    但不巧的是,这些操作又与当前的类有着密切联系(例如会使用到当前类当中的成员(包括私有成员)等等)”这种情况。
    (出自《Java2 学习指南》)

  • 借助内部类实现“多重继承”。也就是假如现在你想要继承多个类,而复用它们的一些功能。
    但由于Java中只允许单继承,就会造成一定麻烦。这个时候,就可以在类中定义多个内部类分别进行继承,从而实现目的。
    (出自《Thinking in java》)

  • 当然就像《Thinking in java》里还谈及有其它更多的使用情况一样,但个人觉得前两种就是内部类被使用的最常见的原因。

基于ESP32与PCM5102的WiFi无损音频传输系统设计与实现 本文详细介绍基于ESP32与PCM5102的WiFi无损音频传输系统设计与实现。通过I2S接口直接传输PCM数据,避免SRC重采样,实现真正的HIFI级音质。涵盖硬件连接、软件架构、网络传输优化及实际音质对比,为音频爱好者提供高性价比的无损无线音频解决方案。 阅读详情

相关推荐

scannet数据集下载文件

scannet数据集下载文件 由于大家会申请scannet数据集,审批比较慢,我将下载的文件里面的内容贴出来,大家可以复制放进py文件中,运行就可以下载 ps:别再私信要数据集了,这个自己下载,如果光三维数据的话,还是很快的,当然如果路由器翻了墙的话,下载会更快。 #!/usr/bin/env python # Downloads ScanNet public data release # Run with ./download-scannet.py (or python download-scannet.

weixin_40766438的博客 9228

“过一会”等于“用不”

今天看书发现了这句话“Later equals never”,发现对自己十分受用。 无论是工作还是学习、生活,我似乎都深受其害。往往对自己“一会再做”的命令,其实就是对事情的推诿和搪塞,是懒惰的体现。 这是个技术博客,但是我希望以此文提醒自己。像爸爸说的,当你想到一件事情,就马上去做。...

chenbeiyou的博客 861

无线充电效率从75%到90%+:深入拆解LCC补偿网络如何成为电动汽车和智能车竞赛的‘稳压器’

本文深入探讨了LCC补偿网络如何显著提升无线充电效率至90%以上,成为电动汽车和智能车竞赛中的关键技术。通过分析LCC的三元件结构和双模式切换特性,揭示了其在负载突变和线圈错位情况下的稳定性优势,并展示了实际应用中的效率提升和产业化挑战。

weixin_42528191的博客 396

《代码整洁之道》学习小结(一)

第一章 整洁代码勒布朗(LeBlanc)法则:稍后等于永不(Later equal never)。要在工作中体现自己的专业性,尤其是和产品或需求方交谈时。代码逻辑直截了当,尽量减少依赖关系,完善错误处理代码,性能调整最优。要在意自己的代码。能通过所有测试,没有重复代码,体现设计理念,包括尽量少的实体(类、方法、函数等)。消除重复(封装重复代码,抽象为类或者方法,具体实现的功能)和提高表达力(使得变

蜗牛先生的博客 1260

浅谈功放PA的VBW

摘要:VBW(视频带宽)反映功放的宽带非线性特性和记忆效应,影响数字预失真(DPD)校正能力。其成因是功放管寄生参数(如Cshunt和Leff)形成低频并联谐振,导致供电臂阻抗增大,引发电压波动和记忆效应。测试方法包括双音IM3/IM5测试(以两侧IM3差≥3dB定义VBW)和魔T测试(通过S21曲线测量谐振频率)。改善措施主要是减小Leff,如采用双电源平衡馈电。VBW指标要求通常为魔T测试结果大于3倍信号带宽,以确保功放线性度。

weixin_49761275的博客 1572

配置 NVIDIA RTX 5090 + sm_120 + flashattention,已跑通一个大模型 ~~

本文介绍了在Ubuntu 22.04系统下为NVIDIA RTX 50系显卡搭建深度学习环境的完整流程。重点包括:1)安装适配50系显卡(sm_120架构)的CUDA 12.9驱动和Toolkit,需注意禁用系统自带驱动;2)配置环境变量解决nvcc命令识别问题;3)通过源码编译方式安装支持sm_120的PyTorch nightly版本(cu129),解决官方二进制包不兼容问题。整个过程涉及显卡驱动安装、CUDA环境配置、PyTorch适配等关键步骤,并提供了详细的验证方法和常见问题解决方案。文中特别强调

qq_36674060的博客 6156

稍后等于永久

稍后等于永久(later equals never.) ------LeBlanc

在路上... 1183

CentOS 8下openLDAP服务器搭建避坑指南:从第三方仓库到phpLDAPadmin配置

本文详细介绍了在CentOS 8环境下搭建openLDAP服务器的完整流程,包括利用Symas第三方仓库解决官方仓库不支持的问题,以及phpLDAPadmin的配置技巧。内容涵盖环境准备、核心配置、安全加固、性能优化等关键环节,帮助系统管理员避开常见陷阱,实现高效稳定的LDAP服务器部署。

weixin_30919429的博客 407

勒布朗法则

稍后等于永不!Later equals never 。 —— [ LeBlanc ]

webrtc 3020

科研级YOLOv8 Neck革新:BiFPN双向特征金字塔赋能,让多尺度融合更高效的实战指南

在文件夹下新建BiFPN.py,将上述BiFPNLayer和BiFPN类的代码写入其中。这一步是为了让YOLOv8的代码架构能识别我们的自定义Neck模块。

m0_64661770的博客 152

别再只当调包侠了!YOLO检测头到损失函数全链路拆解,看完才算真懂

YOLO的精妙不在于某个单一创新,而在于各模块围绕“实时精准检测”这一目标的协同设计。Backbone追求高效特征压缩,Neck保障多尺度信息流通,Head解耦任务冲突,Loss动态引导优化方向。只有理解这套内在逻辑,才能跳出“调包-试错”的低效循环,在面对新场景、新问题时做出有理有据的改进决策。技术深度决定职业天花板。下次再调YOLO时,不妨先问自己:我改的这个参数,到底影响了系统中的哪一环?参考资料。

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 184

《代码整洁之道》

“相对于任何宏伟景愿,对细节的关注甚至是更为关键的专业性基础。首先,开发者通过小型实践获得可用于大型实践的技能和信用度。其次,宏伟建筑中最细小的部分,比如关不紧的门,有点儿没有铺平的地板,甚至是凌乱的桌面,都会将整个大局的魅力毁灭殆尽。这就是整洁代码之所系”----没有比书中的这段话更能说明这本书的意义了。相对于记住那些如何写出整洁代码的那些法则,养成保持代码整洁、提高代码质量的习惯和思维更为重要...

学无止境 944

PostgreSQL核心原理:数据库崩溃恢复的唯一真相详解

PostgreSQL崩溃恢复机制通过WAL日志确保数据一致性和持久性。核心流程包括:触发恢复(非正常关闭时)、读取控制文件获取检查点LSN、顺序扫描WAL日志进行Redo阶段重做已提交事务、利用MVCC标记未提交事务为ABORTED实现逻辑撤销,最后执行检查点完成恢复。该机制依赖WAL原子性、检查点锚点和无Undo的可见性模型三大支柱,保证即使系统崩溃也能恢复到逻辑一致状态。关键设计包括延迟写入、LSN顺序控制和FPW防部分写,最终通过确定性重放实现ACID特性。

数据知道的博客 2658

java面试笔记(二)

在工作流中有一个标识,每一次审批的时候去判断是否审批完成,然后调用反射。

weixin_58316280的博客 371

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

【Java】编程笔记(二)

(3)游戏是无限循环(while true)进行的,只有当谁无法玩游戏时才退出。(2)轮流的实现:turn = turn == 1?(1)赢的规则:轮到谁,谁又无法继续游戏,谁就输了,对方赢了。(1)创建辅助数组help,按列访问matrix,保存值。*:二维数组的操作,马上想到利用一维辅助数组。(3)访问help,给tMatrix按行赋值。(2)创建转置矩阵tMatrix,为n*m。

你不来,再美的天地,都狼藉 ~ 283

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

JAVA笔记二

char是16位,Java在内部使用16位的整数表示字符(Unicode编码),char类型的范围0~65536。char也可以用作整数类型,可以将整型字面值赋给char类型的变量,可以在char类型上执行算术运算。第一个单词的首字母是小写,其他单词的首字母大写。将字面值赋给byte或short变量时,如果字面值位于目标类型的范围之内,就不产生错误。默认为double类型,为了指定float字面值,需要使用后缀F或f。26个小写字母、26个大写字母、以及10个数字0-9,其编码是连续的。

m0_75227909的博客 1012

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

Java笔记二

第一章 Java基础 运算符 常用运算符 算术运算符 关系运算符 逻辑运算符 位运算符 赋值运算符 其他运算符 1.1 算术运算符 表格中的实例假设整数变量X的值为10,变量Y的值为20: 操作符 描述 + 加法 - 相加运算符两侧的值 - 减法 - 左操作数减去右操作数 * 乘法 - 相乘操作符两侧的值 / 除法 - 左操作数除以右操作数 % 取余 - 左操作数除以右操作数的余数 ++ 自增: 操作数的值增加1 – 自减: 操作数的值减少1 %前后操作数只

weixin_46224390的博客 283

jAVA笔记二

1.字符编码和字符集 ANSI编码 ​ 不同的国家和地区制定了不同的标准,由此产生了 GB2312、GBK、Big5、Shift_JIS 等各自的编码标准。这些使用 1 至 4 个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文Windows操作系统中,ANSI 编码代表 GBK 编码;在日文Windows操作...

dengyan1183的博客 259

二. Java学习笔记二

二. Java学习笔记二   由于之前有了Cpp面向对象的思想,同时也想快速入门Java,所以Java学习笔记就比较记录一些学习过程中感觉比较重要的东西,以及有参考价值的代码,可能比较零散,主要是帮助自己记忆。 Part1. 对象的内存图   首先是Java创建一个对象对应的内存图,如下所示: 我们可以看到左侧代码中有两个类,分别是Demo01PhoneOne、Phone,其中有相应的方法。在右侧代码中,我们能够看到,在JVM(Java虚拟机)内存有三块分别是,栈、堆、方法区,其功能如下所示: 堆区:

user-Mars的博客 304

Java基础学习笔记二

Java基础学习笔记二6 File1.File类1.1File类概述和构造方法【应用】1.2File类创建功能【应用】1.3File类判断和获取功能【应用】1.4File类删除功能【应用】2.递归1递归【应用】2递归求阶乘【应用】3递归遍历目录【应用】3.IO流1 IO流概述和分类【理解】2字节流写数据【应用】3字节流写数据的三种方式【应用】4字节流写数据的两个小问题【应用】5字节流写数据加异常处理【应用】6字节流读数据(一次读一个字节数据)【应用】7字节流复制文本文件【应用】8字节流读数据(一次读一个字节

awxnn的博客 1098

java 笔记二

六、数组 数组是相同类型数据的有序集合。数组描述的是相同类型的若干个数据,按照一定的先后次序排列组合而成。其中,每一个数据称作一个元素,每个元素可以通过一个索引(下标)来访问它们。数组的三个基本特点: 1. 长度是确定的。数组一旦被创建,它的大小就是不可以改变的。 2. 其元素必须是相同类型,不允许出现混合类型。 3. 数组类型可以是任何数据类型,包括基本类型和引用类型。 数组变量属引用类型,数组也可以看成是对象,数组中的每个元素相当于该对象的成员变量。数组本...

jwtning的博客 581

Java笔记(二)

文献种类:专题技术总结文献 开发工具与关键技术: IntelliJ IDEA、Java 语言 作者: 方建恒 年级: 2020 撰写时间: 2022 年 5 月 28 日 Java笔记(二) 今天我给大家分享一下我的Java笔记, 在开始正式介绍JAVA前,我们来介绍一下我在用的Java开发工具——IntelliJ IDEA, IDEA是一个基于Java的Java开发工具。 IDEA软件分为旗舰版和社区版,旗舰版相比社区版功能会更全面一些, 我使用的就是旗舰版,我们接下来讲ID

weixin_54668937的博客 1024

java笔记②

No.1 Java中基本数据类型大体分为两类 数字类型(byte,short,int,long,float,double,char),布尔类型(boolean); 备注:由于char可以和数字间转换,也可认为大的范围是数字类型的。在Java中其他的都是引用类型(包括数组和枚举) 。 局部变量是定义在方法中的变量,必须要进行初始化,否则不同通过编译 No.2   以下内容来自牛客网“早起吃虫啦 ...

白首穷经 206

java学习小笔记二

java初学,感觉和c语言的初始架构挺像的(题外话,那个module有点像是verilog里面的)笔者今天学习了java的一些基本的小概念,例如数据类型,运算符,数据转换,同时还。

2301_76707170的博客 803

Java并发笔记二

四、互斥同步 Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLock。 synchronized 1.同步一个代码块 它只作用于同一个对象,如果调用两个对象上的同步代码块,就不会进行同步。 对于以下代码,使用 ExecutorService 执行了两个线程,由于调用的是同一个对象的...

梵高话葵 217
上一篇: JAVA 笔记(一)
下一篇: JAVA 笔记(三) 从源码深入浅出集合框架
LaterEqualsNever
博客等级 码龄12年 297粉丝 61原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值