JavaSE———接口、lambda表达式、内部类

本文详细介绍了接口的概念、实现及应用场景,并深入探讨了内部类的特性及其使用方式,包括匿名内部类、局部内部类和静态内部类等。此外,还讨论了lambda表达式和代理的相关内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

接口

主要用来描述类具有哪些功能,而不具体定义这些功能的实现

接口不是类,是对其实现类的一组需求描述

接口中所有方法默认是public abstract修饰的,属性默认是常量(public static final)

public class InterfaceTest implements Comparable<Object> {

	@Override
	public int compareTo(Object o) {
		// TODO Auto-generated method stub
		return 0;
	}

}

所有实现Comparable接口(只定义了compareTo方法)的类都必须在类中定义compareTo方法

接口不可以创建实例对象,所以接口中不能有实例域(类创建的对象,对象的属性),但是可以定义常量

实现接口:将类声明为实现给定的接口、对接口中的所有方法进行定义。关键字——implements

想要比较不同的两个子类对象,可以在公共父类实现Comparable接口,在其中定义final的compareTo方法

接口没有构造方法,不可以创建对象,但可以声明接口的变量(多态的另一种体现:接口变量引用实现接口的类对象)

可以使用instanceof判断某个对象是否实现了某接口:anObject instanceof Comparable

接口可以使用extends关键字继续扩展:public interface Inter2 extends Inter1{}

每个类只能继承一个类,但是可以实现多个接口:class Emp implements Cloneable,Comparable{}

接口和抽象类的区别和联系:

抽象类仍然是类,只能被继承一个,而多个接口可以同时被实现

抽象类不能创建对象,但是有构造方法(为了让子类调用实例化对象),接口没有构造方法(域都是静态常量)

抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象

抽象类要被子类继承,接口要被类实现

接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现

接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量

抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类

抽象方法只能申明,不能实现,接口是设计的结果 ,抽象类是重构的结果

抽象类里可以没有抽象方法

如果一个类里有抽象方法,那么这个类只能是抽象类

抽象方法要被实现,所以不能是静态的,也不能是私有的

interface TestInter{
	String a = "";
	Object obj = new Object();
	public static TestInter get(){
		return null;
	}
}
java SE 8中,可以在接口中定义静态方法(类方法),java SE 8之前都是讲静态方法放在接口的伴随类(工具类)中,所以jdk1.8之后可以在接口中定义工具方法,不需要单独为接口创建伴随的工具类

默认方法,接口方法的默认实现,default修饰

接口被实现后,添加一个默认方法,实现接口的类重新编译不会报错,如果这个类没有重新编译而直接加载的话,也可以直接调用新增的这个默认方法

如果一个接口中定义一个方法为默认方法,然后又在超类或者另一个接口中定义了同样的方法,解决方式如下:

1.超类优先——如果超类中有这个方法,那么就忽略接口中的方法定义

类优先的规则使得我们在接口中新增一个默认方法,可以确保之前的正常代码不会受到影响

public class InterfaceTest extends TestClass implements TestInter {
	public static void main(String[] args) {
		InterfaceTest it = new InterfaceTest();
		int age = it.getAge();
		System.out.println(age);//2
	}
}

interface TestInter{
	default int getAge(){
		return 12;
	}
}

class TestClass{
	public int getAge(){
		return 2;
	}
}
2.接口冲突——如果同时实现的两个接口中都定义了相同的默认方法,必须覆盖这个方法来解决冲突
public class InterfaceTest implements TestInter ,TestInter2{
//会报错,Duplicate default methods named getAge with the parameters () and () are inherited from the types TestInter2 and TestInter
	public static void main(String[] args) {
		InterfaceTest it = new InterfaceTest();
		int age = it.getAge();
		System.out.println(age);
	}
}

interface TestInter{
	default int getAge(){
		return 12;
	}
}

interface TestInter2{
	default int getAge(){
		return 2;
	}
}

提示我们必须解决这个二义性问题,只要在InterfaceTest类中提供一个getAge方法就可以了,可以选择两个冲突方法的一个:

public int getAge(){
	return TestInter.super.getAge();
}

所以在接口中使用默认方法重新定义Object类中的方法是没有意义的

回调,callback,指定某个特定事件发生时执行相应动作

比较器,实现了Comparator接口的类的实例

public class InterfaceTest implements Comparator<String>{

	@Override
	public int compare(String o1, String o2) {//定义按照字符串长度比较大小的比较方法
		return o1.length() - o2.length();
	}
	
	public static void main(String[] args) {
		String[] strs = {"abc","abd1","bca12","aab123"};
		Comparator<String> comp = new InterfaceTest();
		for (int i = 0; i < strs.length; i++) {
			for (int j = i+1; j < strs.length; j++) {
				if(comp.compare(strs[i], strs[j]) < 0){
					System.out.println(strs[i]+" < "+strs[j]);
				}
			}
		}
                Arrays.sort(strs, comp);//按照自定义比较方式给String数组strs排序
      }
}

克隆,给实现Cloneable接口的类提供一个安全的clone方法,克隆一个对象,防止改变原对象的状态

对象的实例域可能也是可变的类对象,默认的克隆不会克隆子对象,所以需要自己定义

  1. 默认的clone方法是否能满足要求(域都是基本类型或者不可变的类)
  2. 是否可以在可变的子对象上调用clone方法修补默认的clone
  3. 是否应该使用clone方法

如果满足需求中的1、2,就必须:

  1. 实现Cloneable接口
  2. 重新定义clone方法,指定public修饰

每个数组类型都有一个public的clone方法

关于protected修饰符, 在java中,父类中protected权限的成员变量可以被子类访问,但是还是有条件的,具体如下:

子类中可以直接使用父类的protected变量,父类的protected权限的变量可以被子类继承

子类中可以通过子类对象(子类的子类对象也可以)访问父类的protected成员

“同包或者子类可见”的意思是在不同包的情况下,子类中只有子类对象本身可见

子类中使用父类对象或者其他子类对象访问父类的protected成员都是不可以的

父类中的protected成员被子类继承,在子类中创建父类调用这个成员时,调用的是父类自己的protected成员,但在包外不可见,所以无法调用;而子类从父类那里继承了这个protected成员,调用的是自己的protected成员,所以可以使用

lambda表达式

一个可传递的代码块,可以在以后执行一次到多次

表达式形式:参数、箭头(->)以及一个表达式(表达式可以是一个{}代码块)

没有参数时,()不可以省略:()->{System.out.println()}

如果可以推出lambda表达式参数类型,可以省略类型

如果只有一个参数,且类型可以推出,还可以省略括号

        public static void main(String[] args) {
		String[] planets = {"abcasd","aab","abascd","aa"};
		Comparator<String> comp = (String first , String second) -> first.length() - second.length();
		Arrays.sort(planets, comp);
		System.out.println(Arrays.toString(planets));
		
		ActionListener listener = event -> System.out.println("The time is"+new Date());
		Timer t = new Timer(1000, listener);
		t.start();
		JOptionPane.showMessageDialog(null, "Quit program?");
		System.exit(0);
	}

函数式接口:

只有一个抽象方法的接口,需要这种接口对象时,可以提供一个lambda表达式,这种接口就是函数式接口

lambda表达式可以看作是一个函数,而不是对象,只不过这个函数可以转换成接口

方法引用:用::操作符分隔方法名和对象或者类名

object::instanceMethod

Class::staticMethod

Class::instanceMethod

前两种情况等价于提供方法参数的lambda表达式:System.out::println《===》x -> System.out.println(x)

第三种情况,第一个参数会成为方法目标:String::compareToIgnoreCase《===》(x,y) -> x.compareToIgnoreCase(y)

方法引用不能独立存在,总是会转换成函数式接口的实例

可以在方法引用中使用this参数,this::equals《===》x -> this.equals(x)

也可以使用super参数,super::instanceMethod《===》this.super.instanceMethod(params)

lambda表达式中,只能引用值不会改变的变量(无论是lambda表达式中还是外部),即lambda表达式捕获的变量必须实际上是最终变量(初始化后不再赋值)

int a = 1;
a++;
Timer t = new Timer(1000, event -> {
        System.out.println(a);//Error:Local variable a defined in an enclosing scope must be final or effectively final
});
int a = 1;
Timer t = new Timer(1000, event -> {
        a++;//Error:Local variable a defined in an enclosing scope must be final or effectively final
	System.out.println(a);
});

在lambda表达式中使用this关键字,是指创建这个lambda表达式的方法的this参数

public class TestLambda {
	public static void main(String[] args) {
		Timer t = new Timer(1000, event -> {
			System.out.println(this.toString());
		});
		t.start();
		JOptionPane.showMessageDialog(null, "exit OK?");
		System.exit(0);
	}
}


这个this.toString()方法和在main方法中调用this.toString()方法是一样的,this并不是指Timer而是TestLambda对象

lambda表达式中使用this和出现在使用lambda表达式的任意位置一样


内部类

定义在另一个类中的类

使用内部类的目的:

内部类方法可以访问该类定义所在作用域中的所有数据,包括私有数据

内部类可以对同一包下的其他类隐藏

定义一个回调函数且不想编写大量代码时,可以使用匿名内部类

内部类既可以访问自身数据域也可以访问外部类的数据(自身优先,内部类对象有一个隐式引用指向创建它的外部类对象)

public class TestInner {
	private String str = "test";
	public class InnerClass{
		public void test(){
			System.out.println("test,print str in inner:"+str);
		}
	}
	public static void main(String[] args) {
		InnerClass inner = new TestInner().new InnerClass();
		inner.test();//test,print str in inner:test
	}
}
内部类声明为private的,这样就只有外部类可以构造内部类对象
只有内部类可以是private修饰的
使用外部类引用内部类:outerObject.new InnerClass(constructionParams);
内部类中的所有static域都必须是final的,一个静态域只有一个实例,但是每个外部类对象都会分别有一个单独的内部类实例,如果该静态域不是final的,可能就不是唯一的
内部类中不可以有static方法
public class TestInner {
	private String str = "test";
	public class InnerClass{
		public static  int a =1;//Error:The field a cannot be declared static in a non-static inner type, unless initialized with a constant expression
		public static void test(){//Error:The method test cannot be declared static; static methods can only be declared in a static or top level type
			System.out.println("test,print str in inner:");
		}
	}
}
内部类的特殊:
内部类可以访问外部类私有数据,但是常规类不可以访问另一常规类的私有数据
编译器会将内部类翻译成用$分隔外部类名和内部类名的常规类文件:OuterClass$InnerClass.class
编译器翻译外部类时会将外部类当做参数传给一个新增的static方法access$0,然后内部类翻译时会调用这个方法

局部内部类:在块中定义一个内部类

不能使用public或者private进行声明,作用域被限定在声明这个局部类的块中

可以对外部完全隐藏,即使是相同类的其他方法都不可以访问,只有声明这个内部类的方法中才能访问

public class TestInner {
	private String str = "test";
	
	public void test(){
		int a =1;
		System.out.println(a);
		class InnerClass{
			public void printStr(){
				System.out.println("inner:"+str+(a++));
                                //Local variable a defined in an enclosing scope must be final or effectively final
                                //局部类中只可以访问实际上是final的局部变量
                                //java8之前必须将要引用的变量声明为final,java8不需要 
                       }
		}
		InnerClass ic = new InnerClass();
		ic.printStr();
	}
	
	public static void main(String[] args) {
		new TestInner().test();
	        //1
                //inner:test
                //局部内部类既可以访问外部变量又可以访问包含它的块的局部变量
        }
}

匿名内部类:只创建这个类的一个对象,不需要命名,直接使用

格式:new SuperClass(constructionParams){//或者接口

inner class and data

}

public class TestInner {
	private String str = "test";
	
	public void test(){
		int a =1;
		System.out.println(a);
		InnerClass ic = new InnerClass(){//创建一个实现InnerClass接口的类的对象
			public void printStr(){
				System.out.println("inner:"+str);
			}
		};
		ic.printStr();
	}
	public static void main(String[] args) {
		new TestInner().test();
	}
}

interface InnerClass{
	void printStr();
}
构造器必须和类名一致,匿名内部类没有类名,所以没有构造器,都是将构造器参数传给父类构造器(实现接口时不需要任何参数)
静态内部类
只有内部类可以被声明为static,静态内部类的对象不生成外部类对象的引用(不使用外部类的数据)
静态内部类可以有静态方法和静态域
声明在接口中的内部类默认public static修饰
public class TestStaticInner {
	private String str = "123";
	private int a = 1;
	static class InnerClass{
		private static String str = "str";
		static void test(){
			new TestStaticInner().test();
			System.out.println("inner"+str);
		}
	}
	
	public void test(){
		System.out.println("outer"+str);
	}
	
	public static void main(String[] args) {
		new InnerClass().test();
                //outer123
                //innertest
       }
}

代理

使用代理可以实现在运行时创建一个实现了一组接口的新类(编译时无法确定需要实现)

使用场景:

一个表示接口的Class对象(不一定是一个接口),它的确切类型在编译时无法确定。代理类可以在运行时创建一个全新的类,这个类能够实现指定接口(指定接口所需要的全部方法、Object类的全部方法)

不能在运行时定义这些方法的新代码,而是提供一个调用处理器(invocationhandler),调用处理器是实现了InvocationHandler接口(这个接口中只有一个invoke方法)的类对象

创建代理对象

类加载器:使用null表示默认类加载器

Class对象数组:每个元素都是需要实现的接口

调用处理器:实现了InvocationHandler接口(这个接口中只有一个invoke方法)的类对象

class TraceHandler implements InvocationHandler {

	private Object target;
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)//args对应proxy实例时的new Class[]{Comparable.class}
			throws Throwable {
		System.out.print(target);
		System.out.print("."+method.getName()+"(");
		
		if(args != null){
			for (int i = 0; i < args.length; i++) {
				System.out.print(args[i]);
				if(i < args.length - 1){
					System.out.print(", ");
				}
			}
		}
		
		System.out.println(")");
		
		return method.invoke(target, args);
	}

	public TraceHandler(Object target) {
		super();
		this.target = target;
	}
	
}

public class ProxyTest{
	public static void main(String[] args) {
		Object[] elems = new Object[100];
		for (int i = 0; i < elems.length; i++) {
			Integer value = i + 1;
			TraceHandler th = new TraceHandler(value);
			Object proxy = Proxy.newProxyInstance(null, new Class[]{Comparable.class}, th);
			elems[i] = proxy;
		}
		
		Integer key = new Random().nextInt(elems.length) + 1;
		
		int result = Arrays.binarySearch(elems, key);
		
		if(result >= 0 ){
			System.out.println(elems[result]);
		}
	}
}

代理类的特点:

程序运行时创建,一旦创建就变成常规类

所以代理类都扩展了Proxy类,一个代理类只有一个实例域——调用处理器

所有的代理类都覆盖了Object类的tostring、equals、hashCode,Object中的其他方法(clone、getClass)没被覆盖

代理类默认public final修饰

特定的类加载去和相同的一组接口,只能用一个代理类,调用两次newProxyInstance(),会得到同一个类的两个对象


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值