• Java基础


    引言

    本文主要还是记录下内部类的基础语法,具体的作用等接触更多的代码之后回来小结

    定义与用法

    内部类,顾名思义就是在一个类的内部再定义一个类

    public class Outer {
        private String id;
    
        public Outer(String id) {
            this.id = id;
        }
    
        /**
         * 内部类可以是任意的访问权限
         */
        public class Inner {
            // 可以任意的访问外部的属性
            private String innerId = "inner" + id;
        }
    }
    

    访问权限

    1. 内部类本身可以设置任意的访问权限(private,default,protected,public
    2. 内部类可以访问外部类任意权限的属性或方法(普通内部类)
      正是由于内部类可以定义为private,所以内部类的作用之一就是可以对外屏蔽类的存在
      这一点在内部类实现接口时作用会比较明显,例如说Iterator就是一个比较典型的例子
       List<String> list = new ArrayList<>();
       Iterator<String> iterator = list.iterator();
      
      对于客户端来说,你拿到的是一个Iterator,而非ArrayList的内部类对象Itr,你能访问的也只有Iterator接口所提供的方法
      也就是说,ArrayList提供了迭代器的功能,但是完全对客户端屏蔽其实现
    3. 非静态内部类不能定义静态成员,除非它是final(并且是编译期常量)的
      为什么非静态内部类中不能定义静态成员,为什么final又是可以的,参考下面这篇文章:
      为什么非静态内部类中不能有static属性的变量,却可以有static final属性的变量?
    4. 静态内部类不能访问外部类的非静态成员(就好比说静态方法不能访问非静态成员一个意思)

    内外通信

    这一小结主要就是说明访问外部属性以及创建内部类对象的方法

    .this

    普通的内部类(非静态内部类)之所以能够访问外部类的所有属性,肯定是因为有一个应用指向了外部类

    如果你需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟 .this --- 《OnJava8》

    1. 如果直接使用this,则this指向的是内部类的对象,外部类 + .this才是指向外部类的对象
    2. 访问外部属性时并非都要加上.this
      例如下面的代码示例中我们使用Outer.this.getId(),其实直接调用getId()即可
      但有时这时必要的,比如说内部类和外部类的方法、属性重名了,我们在访问外部类时就必须使用 【外部类 + .this】,否则就无限循环了
    public class Outer {
        private String id;
    
        public Outer(String id) {
            this.id = id;
        }
    
        private String getId() {
            return this.id;
        }
    
        private void setId(String id) {
            this.id = id;
        }
    
        /**
         * 内部类可以是任意的访问权限
         */
        public class Inner {
            // 可以任意的访问外部的属性
            private String innerId = "inner" + id;
    
            public String getOutId() {
                return getId();
            }
    
            public void setId(String id) {
                Outer.this.setId(id);
            }
        }
    }
    

    .new

    创建内部类对象时必须要使用外部类的对象进行创建

    public class OuterTest {
        public static void main(String[] args) {
            Outer outer = new Outer("张三");
            Outer.Inner inner = outer.new Inner();
            System.out.println("inner class getId: " + inner.getId());
        }
    }
    

    局部内部类

    有时一个内部类的对象只在某个方法中使用,我们可以在方法中定义内部类
    局部内部类的另一个优点就是可以访问方法中的局部变量(必须事实上是final的,即在匿名类外部赋值后不能再改变了)

    匿名内部类

    在局部内部类的基础上再进一步,有时我们不需要创建多个内部类对象,只需要一个,那么使用匿名内部类会更加方便
    例如我们在Java多线程中使用Runnable接口时就是如此。

    demo

    假如说有一个计算接口

    public interface Calculate {
        int cal(Integer num1, Integer num2);
    }
    

    我们想实现一个加法器

    • 常规内部类写法
    public class Outer3 {
        class Adder implements Calculate{
            @Override
            public int cal(Integer num1, Integer num2) {
                return num1 + num2;
            }
        }
    
        public Calculate getAdder() {
            return new Adder();
        }
    }
    
    

    测试一下:

     Outer3 outer3 = new Outer3();
     Calculate adder = outer3.getAdder();
     int r1 = adder.cal(1,2); // 3
    
    • 匿名内部类写法
    public class Outer3 {
        public Calculate getAdder() {
            return new Calculate() {
                @Override
                public int cal(Integer num1, Integer num2) {
                    return num1 + num2;
                }
            };
        }
    }
    

    同样的测试:

    Outer3 outer3 = new Outer3();
    Calculate adder = outer3.getAdder();
    int r1 = adder.cal(1,2); // 3
    
    • 匿名内部类 + Java8
      由于我们的Calculate只有一个接口,所以我们还可以用Java8的lambda表达式再简化一下
    public class Outer3 {
        public Calculate getAdder() {
            return (num1, num2) -> num1 + num2;
        }
    }
    

    测试方法还是不变

    Outer3 outer3 = new Outer3();
    Calculate adder = outer3.getAdder();
    int r1 = adder.cal(1,2);  // 3
    
    • 直接写在方法里面
      当这个内部类的对象并不需要复用的地方,我们也可以直接在使用到它的地方编写匿名内部类对象
    Calculate adder = (num1, num2) -> num1 + num2;
    int calRes = adder.cal(1,2);   // 3
    

    初始化

    匿名内部类因为没有名字,所以你没有办法通过构造函数进行初始化,但我们还是有一些其他的方法来达到相同的效果

    • 继承一个基类
      还是用上面那个加法器的例子
    public abstract class AbstractCalculate implements Calculate {
        public int baseNum;
        public AbstractCalculate(int i) {
            baseNum = i;
        }
    }
    

    测试一下

      // 此处的 1 可修改为从外部传参
      Calculate adder = new AbstractCalculate(1) {
          @Override
          public int cal(Integer num1, Integer num2) {
              return num1 + num2 + baseNum;
          }
      };
      int res = adder.cal(1,2);  // 4
    

    即通过父类来进行初始化并使用父类的成员

    • 代码块
      继承的方式要求必须有一个父类,有一定的限制,另外一种方法就是通过代码块的方式
    public class Outer3 {
    
        public Calculate getAdder() {
            return (num1, num2) -> num1 + num2;
        }
    
        // 这里我们在初始化的时候做一些复杂的事情,比如初始化一个对象
        public Calculate getAdderWithInit(int base) {
            Calculate adder = new Calculate() {
                Calculate _adder;
                {
                    _adder = getAdder();
                }
                @Override
                public int cal(Integer num1, Integer num2) {
                    return _adder.cal(num1,num2) + base;
                }
            };
            return adder;
        }
    
    }
    
    

    测试一下:

    Calculate adder = new Outer3().getAdderWithInit(2);
    int res = adder.cal(1,2); // 5
    

    静态内部类

    有时我们只是为了把一个类隐藏在另一个类中,并不需要内部类应用外部类的对象,此时我们就可以将内部类声明为static的

    普通的内部类对象隐式地保存了一个引用,指向创建它的外部类对象。然而,当内部类是 static 的时,就不是这样了。

    嵌套类(静态内部类)意味着:

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

    嵌套类与普通的内部类还有一个区别:
    普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有 static 数据和 static 字段,也不能包含嵌套类。
    但是嵌套类可以包含所有这些东西:

    接口内部类

    嵌套类可以作为接口的一部分。你放到接口中的任何类都自动地是 public 和 static 的。因为类是 static 的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。你甚至可以在内部类中实现其外部接口
    如果你想要创建某些公共代码,使得它们可以被某个接口的所有不同实现所共用,那么使用接口内部的嵌套类会显得很方便
    ---- 《OnJava8》

    内部类作用小结

    命名

    使用内部类命名可以避免命名重复的问题
    例如Element这样一个类,你在很多类中可能都会使用到,为了避免重复你可能会使用AElement,BElement这种方式,但是使用内部类则可以使代码结构更加清晰一些

    代码结构

    1. 当两个类的关系特别紧密时,通过内部类可以使得代码结构更加清晰
    2. 同时外部类可以少传递很多参数,因为内部类可以直接访问外部类的成员

    如果不使用外部类,我们就需要使用组合的方式来实现对象的复用,相比使用内部类的缺点:

    1. 内部类的对象其实只有外部类会使用,但如果不使用内部类的方式,你就无法用private来修饰类,最多只能用default,即将这个类暴露给其他类了
    2. 外部类使用内部类的对象方法时需要传递很多参数过去

    访问控制

    即在访问权限中提到过的,可以隐藏内部类,当我的内部类实现了某个接口时,则相当于外部类隐藏了接口的实现,比较典型的就是Iterator

    "多重继承"

    每个内部类都能独立地继承自一个(接口的)实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。 ---- 《OnJava8》

    1. 内部类实现接口:如果内部类只是实现接口,那么由外部类自己实现,功能上其实并没有太大的区别
    2. 内部类继承抽象类:这个就只有内部类才能完成了,因为Java是单继承的体系

    参考资料

    《OnJava8》
    《Java核心技术:卷1》

  • 相关阅读:
    LeetCode Related Info
    LeetCode Climbing Stairs
    Linux命令语句秘籍
    Java基础学习 —— io
    Jquery的入门学习
    Java基础学习 —— bat处理文件
    Java基础学习 —— 线程
    Java基础学习——泛型
    java基础学习——集合
    两个div叠加触发事件发生闪烁问题
  • 原文地址:https://www.cnblogs.com/bax-life/p/14669710.html
Copyright © 2020-2023  润新知