likes
comments
collection
share

Java内部类(超级详细)

作者站长头像
站长
· 阅读数 7

在学习内部类之前,我们先了解一下,类的五大成员是哪些?

属性、方法、构造器、代码块、内部类

内部类就是在一个类或方法中定义的类。内部类又分为成员内部类,静态内部类,匿名内部类和局部内部类。

1、成员内部类

成员内部类是定义在类的内部,作为类的成员的类。

public class Outer {
   private Inner inner=null;
   private double r;
   String b;
   public Inner getInnerInstance(){//用于返回一个内部类对象
        if (inner==null)
            inner=new Inner();
        return inner;
    }
 private class Inner{//成员内部类,用private修饰,只能外部类的内部访问
      final static int t=1;//成员内部类不能定义静态成员,final修饰的除外
       public void draw(){
         System.out.println("绘制一个圆,半径为"+r);
        }
   }
}

特点如下:

  1. 内部类可以直接访问外部类的所有成员(成员变量和成员方法),包括private和static所修饰的。但是外部类不能直接访问内部类成员,需要通过预先创建的内部类对象去访问。
  2. 成员内部类可以使用权限修饰符(private、default、protected、public)任意进行修饰。
  3. 成员内部类是默认包含了一个指向外部类对象的引用。要创建成员内部类对象,必须先创建一个外部类对象。
  4. 成员内部类对象创建方法:
//第一种方式
Outer outer=new Outer();
Outer.Inner inner=outer.new Inner();

//第二种方式
Outer.Inner inner1=outer.getInnerInstance();//在外部类提供一个方法,返回一个内部类对象
  1. 当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:

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

  1. 外部类的静态成员不能访问内部类,内部类不可以定义静态成员(final修饰的除外),比如静态方法、静态属性和静态代码块。

2、静态内部类

使用static修饰的成员内部类我们称之为静态内部类。

public class Test {
    public static void main(String[] args) {
     Outer.Inner inner=new Outer.Inner();//静态内部类可以被其他类直接访问和实例化,而不需要先实例化外部类。
     inner.draw();
    }
}
 class Outer {
  int t=0;
  static  String desc="123";
    static class Inner{
        static int x=10;
        {
            System.out.println("这里是静态内部类的代码块");
        }
        public static void draw(){
            System.out.println("t的值:");//这里会编译报错
            System.out.println("desc的值:"+desc);
        }
    }
}

特点如下:

  1. 静态内部类可以访问外部类的静态成员,不能访问非静态成员,内部类还可以定义静态成员。
  2. 静态内部类是4种类中唯一一个不依赖于外部类对象的引用的内部类,静态内部类可以被其他类直接访问和实例化,不需要先实例化外部类。

3、匿名内部类

匿名内部类没有显式的类名,通常在创建对象的时候定义,可以直接在表达式中使用,不需要单独声明一个命名的类。在jdk8新特性中可以使用Lambda表达式替代。

public class Test {
    public static void main(String[] args) {
     Calculator calculator=new Calculator() {// 创建一个匿名内部类实现Calculator接口
            @Override
            public int calculate(int a, int b) {
                return a + b;
            }
        };
        System.out.println(calculator.calculate(2,3));
    }
}
interface Calculator {
    int calculate(int a, int b);
}

匿名内部类可以出现在任何允许表达式出现的地方,比如方法参数、变量初始化、方法返回值等。定义格式:

new 父类构造器(参数列表)或 实现接口() { //匿名内部类的类体部分 }

特点

  1. 匿名内部类可以访问外部类所有的变量和方法,不能在匿名内部类中修改外部局部变量。
  2. 匿名内部类默认包含了外部类对象的引用。
  3. 使用匿名内部类还有个前提条件必须继承一个父类或实现一个接口
  4. 匿名内部类只能使用一次,它通常用来简化代码编写。

注:匿名内部类是唯一一种没有构造器的类。匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。

使用Lambda进行替换

Lambda表达式并不能取代所有的匿名内部类,能够使用Lambda的依据是必须有相应的函数接口(函数接口,是指内部只有一个抽象方法的接口)。

1.无参函数的简写

如果需要新建一个线程,匿名内部类写法:

new Thread(new Runnable(){// 接口名
	@Override
	public void run(){// 方法名
		System.out.println("Thread run()");
	}
}).start();

Lambda表达式简化写法:

new Thread(
        () -> {     // 省略接口名和方法名
         System.out.print("Hello");  
         System.out.println("Jack"); 
         } 
        ).start();
        
 //如果函数体只有一行语句,花括号直接去掉,留下那一行语句,比如() -> System.out.println("Thread run()")

2.带参数函数的简写

如果要给一个字符串列表通过自定义比较器,按照字符串长度进行排序,匿名内部类写法:

List<String> list = Arrays.asList("I", "love", "you"); 
Collections.sort(list, new Comparator<String>(){// 接口名 
      @Override 
      public int compare(String s1, String s2){// 方法名 
      if(s1 == null) return -1; 
      if(s2 == null) return 1; 
      return s1.length()-s2.length(); 
      } 
      });

Lambda表达式简化写法:

List<String> list = Arrays.asList("I", "love", "you");
Collections.sort(list, (s1, s2) ->{// 省略参数表的类型 
if(s1 == null) return -1; 
if(s2 == null) return 1; 
return s1.length()-s2.length(); });

自定义函数接口

自定义函数接口,只需要编写一个只有一个抽象方法的接口即可。

// 自定义函数接口
@FunctionalInterface  //这个注解是可选的,但加上该标注编译器会帮你检查接口是否符合函数接口规范。
interface MyInterface<T>{
    void doSomething(T t);
}
class Test<T>{
    private List<T> list;
    public void myForEach(MyInterface<T> myInterface){
        for (T t:list) {
            myInterface.doSomething(t);
        }
    }
    public static void main(String[] args) {
        Test test=new Test();
        test.list= Arrays.asList(12,13,14,15,16,17);
        test.myForEach(str->System.out.println(str));// 使用自定义函数接口书写Lambda表达式
    }
}

*需要注意的是:*

lambda表达式隐含了return关键字,所以在单个的表达式中,我们无需显式的写return关键字, 但是当表达式是一个语句集合的时候,则需要显式添加return,并用花括号{ }将多个表达式包围起来,下面看几个例子:

//返回给定字符串的长度,隐含return语句
(String s) -> s.length() 
// 始终返回42的无参方法
() -> 42 
 
// 包含多行表达式,则用花括号括起来
(int x, int y) -> {
    int z = x * y;
    return x + z;
}

4、局部内部类

局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。作用域指的是方法里的代码块(如 if/else 语句块、for 循环块、while 循环块等)。

class Outer{
    public void test(){
        int x=20;
        class Inner{ //在方法内定义
            public void print(){
                System.out.println("我今年"+x);
            }
        }
        new Inner().print();  // 局部内部类必须在方法内部实例化,然后return出去或者直接调用其方法。
    }
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.test();
    }
}

特点如下:

  1. 局部内部类不能有访问权限修饰符,且不能被定义为static。
  2. 内部类可以直接访问外部类的所有成员(成员变量和成员方法)。
  3. 局部内部类默认包含了外部类对象的引用
  4. 局部内部类也可以使用Outer.this语法制定访问外部类成员

5、内部类作用

  • 可以实现多重继承。(最大的优点)
  • 内部类提供了更好的封装,除了该外围类,其他类都不能访问。
  • 内部类拥有外围类的所有元素的访问权限。
  • 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类。

成员内部类实现多继承:

class Father {
    public int eat(){
        return 10;
    }
}
class Mother {
    public int fly(){
        return 20;
    }
}
 class Son {
    class Father_1 extends Father{
        public int eat(){
            return super.eat() + 10;
        }
    } 
    class Mother_1 extends  Mother{
        public int fly(){
            return super.fly() - 7;
        }
    }  
    public int geteat(){
        return new Father_1().eat();
    }  
    public int getfly(){
        return new Mother_1().fly();
    }
}
public class Test {
    public static void main(String[] args) {
        Son son = new Son();
        System.out.println( son.geteat());
        System.out.println( son.getfly());
    }
}