Java内部类与lambda表达式

2023-02-03 08:17:58

最复习的时候发现内部类已经有部分知识开始遗忘了。所以特地抽出时间学习内部类。

1.为什么需要使用内部类

大部分的时候,类被定义成一个独立的程序单元。内部类就是把一个类放在另一个类的内部,包含内部类的类也被称为外部类。

内部类主要有以下作用:

a.内部类提供更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类.

b.内部类成员可以直接访问外部类私有数据,因为内部类被当做其外部类成员,同一个类的成员之间可以相互访问.但外部类不能访问内部类的实现细节,例如内部类的成员变量.

c.匿名内部类适合用于创建那些仅需要使用一次的类

内部类和外部类主要有两种区别

a.内部类比外部类多三个修饰符,private 、protected、static

b.非静态内部类不能拥有成员变量


如下是非静态内部类的使用

package innerclass;

public class Cow {
	private double weight;
	
	public Cow() {}
	
	public Cow(double weight) {
		this.weight = weight;
	}
	
	private class CowLeg{
		private double length;
		private String color;
		
		public CowLeg() {}
		
		public CowLeg(double length,String color) {
			this.length = length;
			this.color = color;
		}
		
		public void info() {
			System.out.println("color:"+color+" length:"+length+" weight:"+weight);
		}
		
		
		
	}
	public void test() {
		CowLeg cl = new CowLeg(1.12,"black and white");
		cl.info();
	}
	public static void main(String[] args) {
		Cow cow = new Cow(378.9);
		cow.test();
	}

}

当在非静态内部类的方法内访问某个变量时,系统优先在该方法内查找是否存在该名字的局部变量,如果存在就使用该变量;如果不存在,则在该方法所在的内部类查找是否存在该名字的成员变量,如果存在则使用;如果还是找不到,那就查找该内部类的外部类是否存在该变量,存在则使用;

另外,非静态内部类里不能有静态方法、静态成员变量、静态初始块,例如下面都是错的

package innerclass;

public class InnerNoStatic {
	
	private class InnerClass{
		
		static {
			System.out.println("");
		}
		
		private static int inProp;
		private static void test() {}
	}

}

2.静态内部类

如果使用static来修饰一个内部类,则这个内部类就属于外部类本身,而不属于外部类的某个对象。

静态内部类可以包含静态成员,也可以包含非静态成员。根据静态成员不能访问非静态成员的规则,静态内部类不能访问外部类的实例成员,只能访问外部类的类成员。即使是静态内部类的实例方法也不能访问外部类的实例成员。

如下是错误的

package innerclass;

public class StaticInnerClassTest {
	
	private int prop1 = 5;
	private static int prop2 = 5;
	
	static class StaticInnerClass{
		private static int age;
		
		public void accessOuterProp() {
			System.out.println(prop1);
		}
	}

}

输出prop1是错误的,不能通过编译.

除此之外Java还允许在接口里定义内部类,接口里默认是public static 修饰的,也就是说接口里是静态内部类

如果省略接口内部类访问控制符,则默认是public

3.使用内部类

分三种情况讨论内部类的用法

1.在外部类内部使用内部类:

在外部类内部使用内部类和平常使用普通的类没有什么区别,唯一的区别就是不要在外部类的静态成员(包括静态方法和静态初始块)中使用非静态内部类。

2.在外部类以外使用非静态内部类

如果希望在外部类以外的地方访问内部类(包括静态和非静态两种),则内部类不能使用private访问控制权限。

a.省略了访问控制符的内部类,只能被外部类或处于同一个包中的其他类访问。

b.使用了protected修饰的内部类,可被与外部类处于同一包中的其他类和外部类的子类所访问。

c.public修饰的内部类,可以在任何地方呗访问。

变量语法的格式如下:

OuterClass.InnerClass varName

由于非静态内部类的对象必须寄生在外部类的对象里,因此创建非静态内部类对象之前,必须先创建其外部类对象。

OuterInstance.new InnerConstructor()

3.在外部类以外使用静态内部类

因为静态内部类是外部类类相关的,因此创建静态内部类对象是无需创建外部类对象。在外部类以外的地方创建静态内部类实例的如法如下:

new OuterClass.InnerConstructor();

4.局部内部类

如果把一个内部类放在方法里定义,则这个内部类就是一个局部内部类,局部内部类仅在该方法里有效。

如下是一个局部内部类的简单用法

package innerclass;

public class LocalInnerClass {
	public static void main(String[] args) {
		
		class InnerBase{
			int a;
		}
		
		class InnerSub extends InnerBase{
			int b;
		}
		
		InnerSub is = new InnerSub();
		is.a = 5;
		is.b = 8;
		
	}

}

实际上局部内部类是一个非常“鸡肋”的语法,现实中很少去使用。

5.Java8改进的匿名内部类

匿名内部类适合创建那些要一次使用的类

定义匿名内部类的语法如下:

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

从上面定义可以看出匿名内部类必须继承一个父类,或实现一个接口,但最多只能继承一个父类和继承一个接口。

匿名内部类还有如下两条规则:

1.匿名内部类不能是抽象类,这个就不说了为什么了。。。

2.匿名内部类不能定义构造器。由于匿名内部类没名字啊,哥哥姐姐们,所以肯定不能有构造器啊。

如下是一个匿名内部类的简单用法:

package innerclass;

interface Product{
	public double getPrice();
	public String getName();
}
public class AnnotmousTest {
	
	public void test(Product p) {
		System.out.println("buy a :" + p.getName() +" cost " + p.getPrice());
	}
	
	public static void main(String[] args) {
		AnnotmousTest ta = new AnnotmousTest();
		
		ta.test(new Product() {
			
			@Override
			public double getPrice() {
				// TODO Auto-generated method stub
				return 567;
			}
			
			@Override
			public String getName() {
				// TODO Auto-generated method stub
				return "hahaa";
			}
		});
	}

}

当创建匿名内部类时就必须实现接口或抽象父类里的所有抽象方法。

在Java8之前,Java要求被局部内部类,匿名内部类访问的局部变量必须使用final修饰,从Java8这个就被取消了,如果局部变量被匿名内部类访问,那么该局部变量相当于自动使用了final.

如下

package innerclass;

interface A{
	void test();
}
public class ATest {
	
	public static void main(String[] args) {
		int age = 8;
		A a = new A() {
			
			@Override
			public void test() {
				// TODO Auto-generated method stub
				System.out.println(age);
			}
		};
		
		a.test();
		
		//加了下面将会导致编译错误
		//age = 2;
	}

}

6.Java8新增的Lambda表达式

Lambda表达式将代码块作为方法参数(很大原因在于优化匿名内部类)

我们看个例子:

// Java 8之前:
new Thread(new Runnable() {
    @Override
    public void run() {
    System.out.println("Before Java8, too much code for too little to do");
    }
}).start();

//Java 8方式:
new Thread( () -> System.out.println("In Java8, Lambda expression rocks !!") ).start();

可以看出,lambda表达式简化了匿名内部类,不需要指出重写的名字,也不需给出重写的方法的返回值类型--只要给出重写的方法括号以及括号里的形参列表即可。

Lambda由三部分组成:

1.形参列表。如果形参列表只有一个参数,甚至连形参列表的圆括号也可以省略。

2.箭头。

3.代码块。如果代码块只包含一条语句,Lambda表达式允许省略代码块的花括号,Lambda代码块只有一条return语句,甚至可以省略return关键字。如果代码块只有一条语句,Lambda表达式会自动返回这条表达式的值。

如下是Lambda表达式的

package innerclass;

interface Eatable {
	void taste();
}

interface Flyable {
	void fly(String weather);
}

interface Addable {
	int add(int a, int b);
}

public class LambdaTest {

	public void eat(Eatable e) {
		System.out.println(e);
		e.taste();
	}

	public void drive(Flyable f) {
		System.out.println("I drive " + f);
		f.fly("好天气");
	}

	public void test(Addable add) {
		System.out.println("The sum of five add three is " + add.add(5, 3));
	}

	public static void main(String[] args) {
		LambdaTest lt = new LambdaTest();
		
		
		lt.eat(() -> System.out.println("hahaha"));
		
		lt.drive(weather ->{
			System.out.println("the wheather is " + weather);
		});
		
		lt.test((a,b) -> a + b);
		
		
	}

}

7.Lambda表达式与函数式接口

Lambda表达式的类型,也被称为“目标类型”,Lambda表达式的目标类型必须是函数式接口。函数式接口代表只包含一个抽象方法的接口。函数式接口可以包含多个默认方法,类方法,但只能声明一个抽象方法。

(Java8有专门的注解@FunctionalInterface来声明,如果不是函数式接口就会报错。)

package innerclass;

public class Test {
	
	public static void main(String[] args) {
	
		Runnable r = () ->{
			
			System.out.println("hahaa");
				
			};
		}
	}

从上面的讲解,和上面的例子可以看出Lambda有如下两个限制。

a.Lambda表达式的目标类型必须是明确的函数式接口

b.Lambda表达式只能为函数式接口创建对象

package innerclass;

public class Test {
	
	public static void main(String[] args) {
	
		
		//false example
		Object obj = () -> System.out.println("hahah");;
		
		
		}
	}

如上这是错误实例,因为Lambda表达式只能为函数式接口创建对象。

为了保证Lambda表达式的目标类型是一个明确的函数式接口,有三种做法

a.将表达式赋值给函数式接口类型的变量

b.将表达式作为函数式接口类型的参数传给某方法

c.对表达式进行强转

如下例子

		Object obj = (Runnable) () -> System.out.println("hahah");


  • 作者:WLNSSSSSS
  • 原文链接:https://blog.csdn.net/m0_38090156/article/details/79493515
    更新时间:2023-02-03 08:17:58