自学Java核心技术(四)之接口,lambada表达式与内部类

接口技术:这种技术主要是用来描述类具有什么功能,而不需要给出每个功能的的具体实现,一个类可以实现一个或是多个接口,并在需要接口的地方,随时使用实现了相应接口的对象

lambada表达式:这是一种表示可以在将来某个时间点执行的代码块的简洁方法,使用lambda表达式,可以用一种精巧而简介的方式来表示使用回调或是变量行为的代码

内部类机制:内部类定义在另一个类的内部,其中的方法可以访问包含它们的外部域,内部类技术主要设计具有相互协作关系的类集合

代理:这是一种实现了任意接口的对象,代理是一种非常专业的构造工具,它可以用来构建系统级的工具

一.接口

1.概念:在Java种,接口不是类,而是对类的一组需求的描述,这些类要遵从接口描述的统一格式,也就是说如果某个类遵从某个特定的接口,那么就完成这个服务

如Arrays中的sort方法可以对对象数组进行排序,但是需要要求对象所属的类必须实现了Comparable接口

public interface Comparable {

int compareTo(Object object);//在类中这个方法必须比较两个对象的内容,并返回比较的结果,当x小于y时,返回一个负数,等于时返回一个0,不然就是返回一个正数

}

在Java5中,这个接口以及改进为泛型了,实现Comparable<Employee>接口的类中,必须提供 int compareTo(Employee other)方法,也可以使用不带类型参数的原始Comparable类型,但是这样就需要手动将compareTo方法的这个参数强制转换为所希望的类型

接口中不能有实例域,提供实例域和方法的实现任务应该在实现了接口的那个类来完成,因此可以把接口看成没有实例域的抽象类

在接口声明中,没有将compareTo方法声明为public,这是因为在接口中的所有方法都是自动地是public,但是在实现接口的时候,必须把方法声明为public,不然编译器就会认为这个方法是属于包里可见的

最好我们应该使用泛型Comparable接口提供一个类型参数

class Employee implements Comparable<Employee>{

public int compareTo(Empolyee other){

}

}

要让一个类使用排序服务,那么就必须让它实现compareTo方法

Java是一种强制性语言,在调用方法的时候编译器将会将会这个方法是不是存在,在sort里面可能有下面的代码

if(a[i].compareTo(a[j])>0) {

}

为此编译器必须保证一定有compareTo方法的存在,而如果实现了这个接口,那么就是代表有这个方法的存在

比如对工资进行比较

public int compareTo(Employee other) {

return Double.compare(salary,other.salary);

}

注意:怼于任意的x和y,实现必须能够保证sgn(x.compareTo(y) = -sgn(y.compareTo(x)))

和equals方法一样,这个方法在继承中,也可能会出现问题

就像Manager扩展了Employee,而Employee实现的是Comparable<Employee>,而不是Comparable<Manager>,如果Manager覆盖了compareTo,那么就必须有经理和雇员进行比较的思想,不能只是简单地把雇员转换为经理

class int compareTo(Employ other) {

public int compareTo(Employee other) {

Manager otherManager = (Manager)other;

}

这不符合“反对称”规则,如果x是一个Employee对象,y是一个Manager对象,调用x.compareTo(y)不会抛出异常,它只是将x和y都作为雇员进行比较,但是反过来就会抛出一个ClassCastException,因为雇员不一定是经理

如果子类之间的含义有所不同的话,那么在比较之前应该运用下面的代码进行检查

if(getClass()!=other.getClass()) throw new ClassCastException();

如果存在一种方法,它能够对两个不同的子类进行比较,那么就应该在超类中提供一个compareTo方法,并将这个方法声明为final

假如想按职务来进行比较,那么就应该在超类中添加一个rank方法,使得每个不同的子类返回不同的东西,然后并实行一个考虑rand的compareTo方法

2.接口的特性

1)接口不能用new来实例化一个接口,但是可以声明一个接口的变量,Comparable x;但是接口变量必须引用实现了接口的类对象

2)instanceof可以用来检查一个对象是不是属于某个接口

3)接口也是可以扩展的,如下面

public interface Moveable{

void move(double x,double y);

}

public interface Powered extends Moveable {

double millopp();

}

4)接口中不能含有实例域或是静态方法(java8之前),但是却可以包含常量,与接口中的方法一样被自动设置为public,域将被自动设置为public static final 

3.接口和抽象类

由于每一个类只能扩展一个类,但是每个类都可以实现多个接口,这就可以避免多重继承的复杂性和低效性

4.静态方法

java8中接口可以添加静态方法,但是这个有违背把接口作为抽象类的起初目的

通常的做法是将静态方法放在伴随类中,在标准库中,我们可以看见有成对出现的接口和使用工具类

如Collection/Collections

不过现在我们不需要为使用工具方法另外提供一个伴随类,可以直接在自己的接口中添加静态方法

5.默认方法

可以为接口提供一个默认的实现,必须用default修饰符中标记这样一个方法,默认方法可以调用任何其他的方法

default int compareTo(T o) { return 0}

有些时候接口中会拥有许多方法,但是有时我们只需要其中的几个,那么我们可以选择将这些方法设置为默认方法,这样我们只要关心我们需要关心的那几个就可以(也就是说默认方法不要求实现了那个接口的类必须去实现它)

默认方法的一个重要作用就是接口演化,假如我们在很久之前就用一个类去实现一个接口,但是后面这个接口又添加了一个steam方法,假如这个方法不是默认方法,那么我们之前的那个类不能编译,因为它没有实现这个新方法,但是如果是默认方法的话,那么就可以正常编译

6.解决默认方法冲突

假如我们在一个接口中将一个方法定义为默认方法,然后又在超类或是另一个接口中定义了同样的方法,那么对此Java有着下面的规则;

1)超类优先,如果超类提供一个具体方法,同名而且有相同的参数类型的默认方法就会被忽视

2)接口冲突,如果一个接口提供了一个默认方法,另一个接口提供了一个同名而且参数类型相同的方法,那么必须覆盖这个方法来解决冲突

如Person和Named接口中都有getName()方法

那么

class Student implements Person,Named {

public String getName(){ return Person.super.getName();}

}

3)类优先原则

如果一个类继承了超类又实现了接口,其中有出现命名冲突的话,那么我们将忽视接口中的所有默认方法,这种规则可以确保和Java7的兼容性,如果为某个接口添加一个默认方法,着对于之前就有这个默认方法的代码不会有任何影响

二.接口的实例

1.接口与回调

回调是一种设计模式,在这种模式中可以指出某个事件发生时应该采取的动作,如按下某个按钮后会出现什么动作

1)首先需要在接口中定义一个方法

2)新建立一个类去实现这个方法

3)然后构造这个类的一个对象,把它传给它应该运用的一个构造器中

2.Comparator接口

在前面我们已经学习了如何对一个对象数组进行排序,前提是这些对象是实现了Comparable接口中的类的实例,可以对字符串数组进行排序,因为String类里面实现了Comparable<String>,这时它是按照字典的顺序来进行排序的

但是如果我们想按照字符串增长的顺序进行排序,那么我们就应该采用另外一个版本的

这个Arrays.sort方法还有第二个版本,有一个数组和一个比较器(compator)作为参数,比较器是实现了Comparator接口的类的实例

public interface Comparator<T>

{

int compare(T first,T second);

}

1)先建立一个类来实现

class LengthComparator implements Comparator<String> {

public int compare(String first,String second) {

return first.length()-second.length();

}

2)建立一个实例

Comparator<String> comp = new LengthComparator();//尽管它没有状态,但是还是需要建立实例,因为它不是静态方法

if(comp.compare(word[i],word[j])>0)...

}

下面是对数组进行排序

Stirng[] frends = {"rddf","dffefv","jfoenf"};

Arrays.sort(friends,new LengthComparator());

3.对象克隆

这边我们来讨论Cloneable接口,这个接口指示了一个类提供了一个安全的clone方法

如果我们为一个包含对象的引用的变量创建一个副本,那么此时由于原变量和副本都是同一个对象的引用,那么任何一个变量的改变都会影响另一个变量,

Employee original = new Employee("hdoan",500000);

Employee copy = original;

copy.raiseSalary(10);

如果我们希望copy是一个新对象,它的初始状态和original相同,但是之后他们可以有不同的状态,那么这个应该就应该使用clone了

Employee copy = original.clone();

copy.raiseSalary(10);

clone方法是Object的一个protected方法,这就说明我们不能直接在代码上直接调用这个方法,只有Employee对象才可以克隆Employee对象,这是因为Object类都不知道以后继承它的类需要clone什么

注意:如果是对对象中的所有数据域都是数值或是其他基本类别,拷贝这些域是没有问题的,但是如果对象包含子对象的引用,那么拷贝域还是得到相同子对象的另一个领域,这样以来,原对象和克隆对象还是会共享一些东西,这也是叫做浅拷贝

如果原对象和浅克隆的对象共享的子对象是不可变的,那么这种共享是安全的,如是一个String的对象,或是子对象都是不变的常量,这也是安全的

不过子对象通常是可以变化的,所以我们必须重新定义clone方法来建立一个深拷贝,也就是同时克隆所有子对象

对每一个类的说需要:

1)默认的clone方法是不是满足要求

2)是不是可以在可变的子对象上调用clone来修补默认的clone方法

3)是不是该使用克隆

如果选择1或2,那么就需要

1)实现Cloneable接口

2)重新定义clone方法,并指定public修饰符//这是因为子类只能调用受保护的方法来克隆它自己的对象,使用public是为了来克隆子对象

这里Cloneable接口的出现和接口正常的实现没有关系,具体来说就是它没有指定clone方法,这个方法是从Object类继承的,这个接口只是作为标记,如果一个对象需要克隆,但是没有实现这个接口,那么就会生成一个受检异常

Cloneable是一个标记接口,标记接口不含任何方法,它的唯一作用就是用来允许在类型查询中查询使用instanceof

即使默认的克隆(浅拷贝)可以满足要求,还是需要实现Cloneable接口,将clone重新定义为public,再调用super.clone();

class Employee implements Cloneable {

public Employee clone() throws CloneNotSupportedException {

return (Employee)super.clone();

}

}

要进行深拷贝,还需要克隆对象中可变的实例域

public Employee clone() throws CloneNotSupportedException  {

Employee cloned = (Employee)super.clone();

cloned.hiredDay = (Date)hireDay.clone();

return cloned;

}

在一个对象上调用clone,但是这个对象的类没有实现Cloneable接口的话,那么Object类的clone方法就会抛出这个异常,就算类有实现这个接口,有时编译器还是会不知道,所以我们需要来声明这个异常

其实也可以用捕获,不过最好还是保留throws说明符好

注意:所有的数组中都包含一个public的clone方法,而不是protected

int[] lucky =  {3,2,3,3,1,5,4};

int[] cloned = lucky.clone();

cloned[5] = 12;

三.lambda表达式(自带参数变量的表达式)

1.为什么使用lambda表达式

lambda表达式是一个可以在以后执行一次或是多次的可传递的代码块

如 Arrrays.sort(strings,new LengthComparator())

在上面我们可以理解为是把一个代码块传递到某个对象,这个代码块会在将来的某个时候使用,之前我们不能直接传递代码段,因为Java是一种面向对象的语言,所以它必须构造一个对象,这个对象的类中需要有一个方法能包含所需要的代码块

2.lambda表达式的语法

1)一种格式是:参数,箭头(->)以及一个表达式,如果要完成的计算无法放在一个表达式中,那么就可以像写方法一样,把这些代码放在{}中,并包含显式的return语句 //()里面的是输入参数

(String first,String second)->{

if(first.length()<second.length()) return -1;

else if (first.length()>second.length()) return 1;

else return 0;

}

2)即使lambda表达式没有参数,仍然需要提供空括号,就像无参方法一样

()->{for (int i=0;i<100;i++) System.out.println(i);}

3)如果可以推导出lambda表达式的参数类型,那么就可以忽视它的类型

Comparator<String> comp = (first,second)->first.length()-second.length();//这边最后是给一个字符串比较器的,所以可以推断出是字符串的

4)如果方法只有一个参数,而且这个参数的类型是可以推导出来的,那么甚至可以省掉小括号

ActionListener listener = event->System.out.println("hdih" + new Date());

5)无需指定lambda表达式的返回类型,lambda表达式的返回类型总是会由上下文来推导出来的

注意:如果lambda表达式只有在一些分支上有反回值,一些没有的话那么就是不合法的

3.函数式接口

在前面我们以及知道了Java中有许多封装代码块的接口,如ActionListener,lambda表达式与这种接口是兼容的

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

下面来转换为函数式接口:

Arrays.sort(words,(first,second)->first.length()-second.length());

在底层Arrays.sort方法会接受实现了Comparator<Stinrg>的某个类的对象,在这个对象上调用compare方法会执行这个lambda表达式的体,这些类和管理完全取决于具体的实现

lambda表达式可以换成接口,

Timer t = new Timer(1000,event->{

System.out.println("ddddsc" + new Date());

Toolkit.get();

})

lambda表达式只能转换为函数式接口

JavaSE建立了一个思路:想要用lambda表达式做某些处理,那么需要知道这个表达式的用途,然后为它建立一个特定的函数式接口

如Java中有这样的一个接口

public interface Predicate<T> {

boolean test(T t);

}

ArrayList类中有一个removeIf的方法,它的参数就是一个Predicate,这个接口就是专门用来传递lambda表达式的,

list.removeIf(e->e==null);

4.方法引用

如果我们想要只要出现一个定时器事件就打印这个事件对象,那么就可以使用

Timer t = new Timer(1000,event->System.out.println(event));

可以写成

Timer t = new Timer(1000,System.out::println);

System.out::println是一个方法引用,它等价于lambda表达式的x->System.out.println(x);

我们可以用 ::操作符分隔方法名和对象或是类名,主要有三个情况

1)object::instanceMethod

2)Class::staticMethod

对于这两种,方法引用等于提供方法参数的lambda表达式,如Math::pow等价于(x,y)->Math.pow(x,y);

3)class::instanceMethod

对于这种来说,第一个参数会成为方法的目标,如; String::compareToIgnoreCase等于(x,y)->x.compareToIgnoreCase(y);

4)方法引用也可以使用this参数,如 this::equals等同于 x->this.equals(x);

super也可以用,它会调用超类的方法

5.构造器引用

构造器引用与方法引用类似,只不过方法名是new,如:Person::new是Person构造器的一个引用,而是哪一个构造器,这取决于上下文

可以为数组类型建立构造器引用,如int[]::new是一个构造器引用,它有一个参数就是数组的长度

不过Java无法构造泛型类型T的数组

6.变量作用域(有时我们希望在lambda中访问外围方法或是类中的变量)

lambda表达式的理解:lambda有三部分

1)一个代码块

2)参数

3)自由变量的值,这里指的是非参数而且不在代码中定义的变量,这边指的是下面代码的text

如下面的代码:

public static void repeatMessage(String text,int delay) {

ActionListener listener = event->{

System.out.println(text);

Toolkit.getDefaultToolkit().beep();

}

new Timer(delay,listener).start();

}

这边我们是把一个lambda表达式转换为包含一个方法的对象,这样自由变量的值就会复制到这个对象的实例变量中

在上面我们可以知道,lambda表达式可以捕获外围作用域的值,但是这边有一个限制:就是在lambda中,只能引用值不会改变的变量,之所以这么限制,是因为如果在lambda表达式中改变变量,并发执行多个动作时就会不安全,还有就是,如果在lambda中引用变量,而这个变量可能在外部改变,这也是不合法的

这里有一条规矩:lambda中捕获的变量必须实际上是最终变量,实际上的最终变量是指,这个变量初始化后就不会再为它赋新值了,如果符合的话,就可以捕获那个变量

1)在方法中不能有两个同名的局部变量,因此lambda表达式也不能有同名的局部变量

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

public class Application {

public void init(){

ActionListener listener = event->{

System.out.println(this.toString());

}

}

这里的this.toString()会调用Application对象的toString()方法,而不是ActionListener实例域的方法

}

7.处理lambda表达式

在前面我们已经知道如何生成lambda表达式和,和把lambda表达式传递到需要一个函数式接口的方法,下面是来编写方法处理lambda表达式

使用lambda表达式的重点就是为了延迟执行,因为如果想直接执行代码,完全可以执行,不用把它包装到lambda表达式里面

之所以想要以后再执行代码,原因如下面

1)只有在必要时才运行代码

2)发生某些情况才执行

3)多次运行代码

如下面的例子

想要重复一个动作n次,

repeat(10,()->System.out.println("hello"));

要接受这个lambda表达式,那么就需要一个函数式接口

public static void repeat(int n,Runnable action) {

for(int i=0;i<n;i++) action.run();

}

调用action.run()时会执行这个lambda表达式的主体

对此i,Java有提供了多个函数式接口,可以通过查文档来了解各个函数接口的用法

注意:如果我们自己来设计自己的接口,其中只有一个抽象方法,可以@FunctionalInterface注释这个接口,这样有两个优点:(1)多一个接口的话会报错(2)Javadoc文档里面会指出这是一个函数式接口

四.内部类

内部类是定义在另一个类中的类

如:

public class TalkingClock {

public class TimePrinter implements ActionListener {

}

}

内部类在外部类里面,但是不能证明每个外部类都有一个内部类的实例域,因为内部类对象可能是在外部类的方法中构造的

使用内部类的作用:

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

2)内部类可以对同一个包中的其他类隐藏起来

3)想要定义一个回调函数又不想编写大量的代码,那么使用匿名内部类比较比较容易

1.使用内部类访问对象的状态

内部类可以访问自身的数据域,也可以访问创建它的外围类对象的数据域,为能够运行这个程序,内部类的对象总有一个隐式引用,它指向了创建它的外部类对象,当然这个引用在内部类的定义中是不可见的,其外围内的引用在构造器中设置,编译器修改了所有的内部类的构造器,添加一个外围内引用的参数,如果TimePrinter类没有定义构造器,那么编译器会生成下面默认的构造器

public TimePrinter(TalkingClock clovk) {

outer = clock;

}

ActionListener listener = new TimePrinter(this);//这个是在外部类方法中实现,也就构造了内部类的实例

如果内部类被设置为私有的,那么这个类只能由外部类的方法来调用

2.内部类的特殊语法规则

1)表达式OuterClass.this表示外围类的引用,如TalkingClock.this.beep

2)表达式outerObject.new InnerClass(constructstion parameter)就可以更加明确地编写内部类的构造器

3)可以通过显式地命名将外围类引用设置为其他对象,如TimerPrinter是一个公有的内部类,那么对于任意的语音时钟都可以构造一个TimerPrinter

TalkingClock jabber = new TalkingClock(1000,true);

Talking.TimerPrinter listener = jabber.new TimerPrinter();

注意:(1)内部类中声明的所有静态域都必须是final的,因为我们都希望一个静态域都只能有一个实例,不过对于每一个外部对象来说,都会分别有一个单独的内部类实例,如果这个域不是final的话,那么它可能就不是唯一的(2)内部类不能有静态方法,就有有的话也只能访问外围类的静态域和方法

3.内部类是不是有用和必要与安全

1)内部类是一种编译器现象,与虚拟机无关,编译器会把内部类翻译成用$分隔外部类和内部类,如TalkingClock$TimerPrinter

2)内部类可以访问外部类的私有变量,但是常规类不可以,之所以内部类有这种特权,是因为编译器在编译的时候会为内部类所在的外部类添加一个返回外部类私有变量的静态方法,内部类方法会去调用这个方法,这样就会存在着一个安全的问题:因为任何人都可以通过调用那个静态方法来读取到那个私有的变量,虽然这个的难度很大,但这也说明了如果私有类访问了私有的数据域,那么就可能通过附加在外围类所在的包的其他类来访问它

4.局部内部类

如果一个类只是在一个方法中才调用一次的话,那么可以直接在那个方法中定义局部类,

public void start(){

class TimePrinter implements ActionListener {

public void actionPerformed(ActionEvent event)

 {

System.out.println("ddd");

if(beep) Toolkit.getDefaultToolkit().beep();

}

}

}

局部内部类不能用public和private修饰符来进行声明,它的作用域被限定在声明折耳根局部类的块中

优势:对外界进行了隐藏,只有它存在的那个方法可以使用它,其他的都不可以

5.由外部方法访问变量

与其他内部类相比,局部类有一个优点,就是它不仅可以访问包含他们的外部类,也可以访问局部变量,不过这些局部变量必须是final的,这说明一旦它们被赋值,那么就不会改变,这会使得局部变量与在局部变量中建立的拷贝保持一致,如下面的代码:

public void start( final int interval , final boolean beep) {

class TimePrinter implements ActionListener {

public void actionPerform(ActionEvent event) {

System.out.println("dahds" + new Date());

if(beep) ToolKit.getDefaultToolkit().beep();

}

}

}

这样外部类就不需要存储实例变量了,它只是引用star()方法的beep常量

流程:

1)调用start方法

2)调用内部类的构造器,以便后面初始化对象变量listener

3)将listener引用传递给Timer构造器,start方法结束,此时start方法中的beep变量不再存在

4)然后actionPerformed方法工作,内部类TimerPrinter会在beep域释放之前将beep域用start方法的局部变量进行备份,也就是编译器会为局部内部类构造一个域来存储这个局部变量,如下面的代码就是编译器来构建的

class TalkingClock$1TimePrinter {

TalkingClock$1TimerPrinter(Talking,boolean);

public void actionPerformed(java.awt.event.ActionEvent);

final boolean val$beep;

final TalkingClock this$0;

}

我们可以注意到构造器的参数其实就是方法的final的参数类型,这就说明了当构造一个对象的时候,beep就会传递给val$beep域,编译器必须检测每个局部变量,为final的局部变量建立对应的数据域,并将局部变量拷贝到构造器中去,以便将这些数据初始化为局部变量的副本,使用final使得局部变量与局部类建立的拷贝保持一致

但是有时final限制会显得并不太方便,假如想要更新在一个封闭作用域的计数器,这里使用final就不可以了,由于Integer也是不可变的,那么不能拿来代替它,补救的方法是使用一个长度为1的数组

int[] counter = new int[1];

for(int i =0;i<data.lenth;i++) {

date[i] = new Date();{

public int compareTo(Date other) {

counter[0]++;

return super.compareTo(other);

}

}

}

6.匿名内部类

如果再将局部内部类的使用再深入一步,假如只创建这个类的一个对象,那么就不用命名了,这种类叫做匿名内部类

它的含义是:创建一个实现ActionListener接口的类的新对象,需要实现的方法actionOerformed定义在括号中{}中

public void start(int interval,boolean beep) {

ActionListener listener = new ActionListener(){

public void actionPerformed(

)

};

Timer t = new Timer(interval ,listener);

t.start();

}

通常的格式如下面:

new SuperType(construction parameters){

inner class methods and data

}

其中SuperType可以是ActionListener这样的接口,于是内部类需要去扩展它,于是内部类就要去实现它,SuperType也可以是一个类,于是内部类就要去扩展它

1)由于构造器的名字需要和类名相同,而匿名内部类没有名字,所以匿名内部类没有构造器,取而代之的是将构造器参数传给超类构造器

2)当匿名内部类是接口的时候,不能有任何构造参数,如下

new InterfaceType(){

methods and data;

}

3)如果构造参数的闭小括号后面跟着一个开大括号,那么它就是正在定义匿名内部类

Person count = new Person("dsad") { }

4)多年以来Java程序员已经习惯使用匿名内部类来实现监听器和其他回调了,不过现在最好使用lambda表达式

如之前的

public void start(int interval,boolean beep) {

ActionListener listener = new ActionListener(){

public void actionPerformed(

)

};

Timer t = new Timer(interval ,listener);

t.start();

}

现在可以换成

public void start(int interval,boolean beep) {

Timer t = new Timer(interval,event->{

System.out.println("dafhso" + new Date());

if(beep) Toolkit.getDefaultToolKit().beep();

});

t.start();

}

但是要使用lambda表达式的话,那么就是它所在的那个参数需要是一个接口的对象,这个表达式的编写等于是在那个接口里面的方法编写的,所以可以直接回调它

注意:

1)双括号初始化

假如我们想构造一个数组列表,然后将它传递到一个方法:

ArrayList<String> friends = new ArrayList<>();

friends.add("ss");

friends.add("ddf");

invite(friends);

如果不需要这个数组列表,最好让它作为一个匿名列表

invite(new ArrayList<String>(){{

add("dfds");

add("frfhi");

}})

外层括号建立一个ArrayList的一个匿名内部类,内层括号则是一个对象构造块

2)对于if(getClass()!=other.getClass()) return false;来说,这个对于匿名子类来测试这个会失败

3)调试时,我们可能希望有包含当前的类名,不过这对静态方法没有用,因为调用 getClass时调用的是this.getClass().而静态方法没有this,对此应该采用下面的方法

new Object(){}.getClass().getEnclosingClass();

new Object(){}会进建立Object的一个匿名子类的一个匿名对象,getEnclosingClass()则是得到其外部类

7.静态内部类

有时候如果使用内部类只是需要把一个类隐藏在另外一个类里面,而不需要内部类引用外部类的对象,那么就可以将这个类声明为static,以便消除引用

如我们现在在计算一个数组中的最小值和最大值的问题,我们可以选择编写两个方法,一个计算最小值,一个计算最大值,在调用这两个方法的时候就是把数组遍历两次,如果数组遍历一次,并同时计算出最小值和最大值,那么就可以提高效率

如public static Pair minmax(double[] d) {

double min = Double.POSITIVE_INFINITY;

double max = Double.NEGATIVE_INFINITY;

for(double v : values) {

if(min > v) min = v;

if(max<x) max = v;

}

return new Pair(min,max);

}

上面那个方法返回的值就是这边需要定义的一个static内部类,因为Pair是一个大众化的类名,可能有其他的人已经写了这个类,所以我们应该把它放置在包含这个方法的类中,又由于这个类不需要引用其他任何对象,因此可以将这个内部类声明为static

class ArrayAlg {

public static class Pair{

}

}

然后引用是就可以使用下面的代码,这边就避免引用错类了

ArrayAlg.Pair p = ArrayAlg.minmax(d);

注意:只有内部类才可以声明为static,静态内部类除了没有引用生成它的外围类对象的引用特权外,其他的和其他内部类一样

再者由于我们是在静态方法中构造这个内部类对象,所以我们必须构造静态内部类

2)静态内部类可以拥有静态域和方法

五.代理(只有在编译时无法确定需要实现哪个接口时才有必要使用)

利用代理可以在运行时创建一个实现了一组给的接口的新类,对于程序设计人员可能遇到的机会比较少,但是对于系统程序设计人员来说挺灵活的

1.何时使用代理

假如有一个表示接口的Class对象,它的确切类型在编译时无法知道,那么我们可以来使用代理机制,代理类可以在在运行时创建全新的类,这样的类可以实现指定的接口,它具有下面的方法

1)指定接口所需要的全部方法

2)Object类中的全部方法,如 toString,equals等

这边需要提供一个调用处理器(invacation handler) 调用处理器是实现了InvocationHandler接口的类对象,里面只有一个方法

Object invoke(Object proxy,Method method,Object[] args)

无论何时调用代理对象的方法,调用处理器的invoke方法都会被调用,向他传递Method对象和原始的调用参数

2.创建代理对象

想要创建一个代理对象,需要使用Proxy类的newProxyInstance方法,这个方法有三个参数

1)一个类加载器,可以用null表示使用默认的类加载器

2)一个Class对象数组。每个元素都是需要实现的接口

3)一个调用处理器

如何定义处理器,能够用结果代理对象做什么,这取决于打算用代理机制做什么

原因可能有(1)路由对远程服务器的方法的调用(2)在程序运行时将接口事件和动作关联起来(3)为调试,跟踪方法

猜你喜欢

转载自blog.csdn.net/weixin_41673515/article/details/90667118