查看“核心技术:接口、lambda表达式与内部类”的源代码
←
核心技术:接口、lambda表达式与内部类
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
[[category:JavaCore]] == 接口 == 接口(interface),是对类的一组需求描述(一组行为定义): # 接口的所有方法自动属于public(不用提供public关键字); # 接口中可以有常量,但是不能有实例域; #: 即,域自动设置为:“public static final” # 接口中可以有实现方法(Java SE 8 之后),但方法中不能引用实例域; #: 默认:“public abstract” 提供实例域和方法实现,应该由实现接口的类来完成。<br/> 实现接口: # 声明实现给定的接口: #: <syntaxhighlight lang="java"> class Employee implements Comparable </syntaxhighlight> # 对接口中的所有方法进行定义; * 所有方法都必须有实现; * 实现方法时,必须把方法声明为“public”; === 接口的特性 === # 接口不是类,不能用“new”进行实例化; # 可以用接口声明变量,变量只能引用接口实现类的对象; # 可以使用“instanceof”检查一个对象是否实现了某个特定接口(类似于检查对象是否属于某个类); # 接口可以被继承,用新的接口扩展旧接口; === 接口与抽象类 === Java不支持多重继承(multiple inheritance):引入抽象类,避免多重继承的复杂性和低效性。 === 静态方法 === Java 8 之后,允许接口中增加静态方法: * 静态方法无法实现多态,形式上重写,实际上是新方法。 === 默认方法 === 可以为接口方法提供一个默认实现(也可以不实现),并用“default”修饰该方法: # 可以不用实现接口所有方法,只关注于需要的某个或某几个方法(可以不再使用伴随类): #: <syntaxhighlight lang="java"> public interface MouseListener { default void mousedieked(MouseEvent event) {} default void mousePressed(MouseEvent event) {} default void mouseReleased(MouseEvent event) {} default void mouseEntered(MouseEvent event) {} default void mouseExited(MouseEvent event) {} } </syntaxhighlight> # 在为旧接口增加方法时,使用默认方法可以保证“源代码兼容”(source compatible); #: (增加非默认方法,会导致接口现有实现类不能正常工作) ==== 关于伴随类 ==== <pre> 在JavaAPI 中,你会看到很多接口都有相应的伴随类,这个伴随类中实现了相应接口的部分或所有方法, 如 Collection/AbstractCollection 或 MouseListener/MouseAdapter。在JavaSE 8 中,这个技术已经过时。现在可以直接在接口中实现方法。 </pre> 使用抽象类(伴随类)对接口进行部分实现,而后用户类并不之间实现接口(需要实现全部方法),而是继承与该抽象类(只重写需要的类即可)。如: # <syntaxhighlight lang="java"> public interface Collection<E> extends Iterable<E> { public abstract class AbstractCollection<E> implements Collection<E> { </syntaxhighlight> # <syntaxhighlight lang="java"> public interface MouseListener extends EventListener { public abstract class MouseAdapter implements MouseListener, MouseWheelListener, MouseMotionListener { </syntaxhighlight> # 又见,SpringMVC 中的拦截器(见[http://wiki.eijux.com/SpringMVC%EF%BC%9A%E9%AB%98%E7%BA%A7%E5%BA%94%E7%94%A8#.E6.8B.A6.E6.88.AA.E5.99.A8.E5.AE.9A.E4.B9.89]): #: <syntaxhighlight lang="java"> public interface HandlerInterceptor { public interface AsyncHandlerInterceptor extends HandlerInterceptor { public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor { </syntaxhighlight> === 解决默认方法冲突 === 冲突:现在接口中将一个方法定义为默认方法,又在超类或另一个接口中定义了同样的方法;<br/> 规则: # 超类优先:接口中的方法(不论是否默认方法)会被忽略; # 接口冲突:一个接口提供默认方法,一个接口提供同名同参数方法(不论是否默认方法),则在实现类必须覆盖这个方法; == 接口示例 == === 接口与回调 === 回调的思想是: # 类A的a()方法调用类B的b()方法 # 类B的b()方法执行完毕主动调用类A的callback()方法 * '''回调的核心就是回调方将本身即this传递给调用方''' 回调与接口: # 将回调方法抽象为接口,用回调方去实现需要的回调接口 '''[[关于Java调用]]''' === Comparator 接口 === * 若需要将对象数组排序,则必须数组的每一个元素都实现了'''Comparable 接口''',并实现其 compareTo 方法: *: <syntaxhighlight lang="java"> package java.lang; import java.util.*; public interface Comparable<T> { public int compareTo(T o); } </syntaxhighlight> 然而,如果需要对 String 数组排序(已经实现了 Comparable<String> 接口),但又不想按照字典顺序排序(String.compareTo 的实现是按字典顺序比较): # 首先我们不能使 String 类实现两种不同的 compareTo 方法, # 其次 String 类也不应该由我们修改。 即:'''如何使用 Comparable.compareTo 方法之外的比较方法,来进行对象数组的排序?'''【如何自定义排序】 所以,需要用到:'''Comparator<T> 接口'''(不同于 Comparable): #: <syntaxhighlight lang="java"> package java.util; ... public interface Comparator<T> { int compare(T o1, T o2); ... } </syntaxhighlight> * Comparator<T> 接口的方法很多,但并不用全部实现,因为一部分方法有实现,一部分为默认方法; 示例一:对 String 数组自定义排序: # 定义一个 Comparator<String> 接口的实现类: #: <syntaxhighlight lang="java"> class LengthComparator implements Comparator<String> { public int compare(String first, String second) { return first.length() - second.length(); } } </syntaxhighlight> # 使用 Arrays.sort 进入数组比较: #: <syntaxhighlight lang="java"> String[] friends = { "Peter", "Paul", "Mary" }; Arrays.sort(friends, new LengthComparator()): </syntaxhighlight> #* Arrays.sort 是静态方法,可以类名直接调用; * <syntaxhighlight lang="java" highlight=""> // 对 Arrays 默认排序 public static <T> void sort(T[] a) // 对 Arrays 使用自定义排序(Comparator) public static <T> void sort(T[] a, Comparator<? super T> c) </syntaxhighlight> 示例二:单独对两个对象或两个数组元素进行比较: : <syntaxhighlight lang="java"> Comparator<String> comp = new LengthComparator(); if (conp.compare(words[i], words[j]) > 0) ... </syntaxhighlight> * Comparator.compare 并非静态方法,所以使用时需要 Comparator 实例; '''利用lambda 表达式可以更容易地使用 Comparator''' === 对象克隆 === ==== 拷贝和克隆 ==== [[File:拷贝和克隆.png|400px]] # 为一个包含对象引用的变量建立副本,直接赋值即可,原件副本都是同一个对象的引用; # 若希望为对象也创建一个副本,则应该使用克隆; ==== 浅拷贝(Object.clone()) ==== [[File:浅拷贝.png|400px]] 默认的克隆操作(“Object.clone()”)是“浅拷贝”:如果对象包含子对象的引用,拷贝域会得到相同子对象的另一个引用; * 即,原对象域浅克隆对象会引用相同的子对象; # Object.clone() 是Object的一个protected方法; # 若,子对象属于一个不可变的类,或在生命周期中状态不变,则是安全的;否则不应该使用浅拷贝; ==== 深拷贝(Cloneable 接口) ==== Cloneable 接口时Java提供的一组标记接口(tagging interface,或称记号接口 marker interface): * 标记接口不包含任何方法,它的唯一作用就是允许在类型查询中使用 instanceof; * 即使 clone 的默认实现(浅拷贝)满足要求,还是需要实现 Cloneable 接口,将 clone 重新定义为 public,再调用 super.clone(): <syntaxhighlight lang="java"> class Employee implements Cloneable { // raise visibility level to public, change return type public Employee clone() throws CloneNotSupportedException { return (Employee) super.clone(); } ... } </syntaxhighlight> 深克隆的实现: # 实现 Cloneable 接口,将 clone 重新定义为 public; # 重写 clone 方法,克隆需要的子对象(分别调用子对象的克隆方法); <syntaxhighlight lang="java"> class Employee implements Cloneable { ... public Employee cloneO throws Cl oneNotSupportedExcepti on { try { // call Object, clone0 Employee cloned = (Employee) super.cloneO ; // clone mutable fields cloned.hireDay = (Date) hireDay.clone() ; return cloned; } catch (CloneNotSupportedException e) { return null; } // this won't happen, since we are Cloneable } } </syntaxhighlight> == lambda == === 为什么引入 lambda === lambda :带参数变量的表达式,用于传递一个代码块到某个对象; * Java 是面向对象的语言,所以必须构造一个对象,用对象的类的方法来包含所需的代码块; === lambda表达式的语法 === lambda 表达式形式: '''<syntaxhighlight lang="xml"> 参数 -> 表达式 </syntaxhighlight>''' 如: : <syntaxhighlight lang="java"> (String first, String second) -> { if (first.length() < second.length()) return -1; else if (first.length() > second.length()) return 1; else return 0; } </syntaxhighlight> '''参数:''' # 如果没有参数,仍然要保留括号。 #: 比如: #: <syntaxhighlight lang="java"> () -> { for (int i = 100; i >= 0;i ) System.out.println(i); } </syntaxhighlight> # 如果可以推导出参数类型,可以忽略参数类型。 #: 比如: #: <syntaxhighlight lang="java"> Comparator<String> comp = (first, second) // Same as (String first, String second) -> first.length() - second.length(); </syntaxhighlight> # 如果方法只有一个参数,且参数类型可以推导,可以只保留参数名。 #: 比如: #: <syntaxhighlight lang="java"> ActionListener listener = event -> System.out.println("The time is " + new Date()"); // Instead of (event) -> . . . or (ActionEvent event) -> . . . </syntaxhighlight> '''表达式:''' # 无需指定 lambda 表达式的返回类型。【lambda 表达式的返回类型总是会由上下文推导得出】 #: 比如:如下表达式可以在 int 类型结果的上下文使用: #: <syntaxhighlight lang="java"> (String first, String second) -> first.length() - second.length() </syntaxhighlight> # lambda 表达式不能只在某些分支返回值,而存在分支不返回值。 #: 如下,是不合法的: #: <syntaxhighlight lang="java"> (int x)-> { if (x >= 0) return 1; } </syntaxhighlight> === 函数式接口 === 函数式接口:有且仅有一个抽象方法,但可以有多个非抽象方法 的接口。(即,接口“只有一个需要实现的方法”) * 接口中没有具体实现、没有“public static”修饰的也是抽象方法,因为 '''接口中的方法默认为“public abstract”'''(见“接口”一节的描述); * Java API 在 '''java.util.function''' 包中定义很多非常通用的函数式接口; 需要函数式接口的对象时,可以提供一个 lambda 表达式。【即,用 lambda 表达式来代替函数式接口的抽象方法(“那个唯一需要实现的方法”)】 * 最好把 lambda 看作是一个函数,而不是一个对象; * Java中,'''lambda所能做的也只是能转换为函数式接口'''; 设计函数式接口时,可以使用<big>“'''<span style="color: green">@FunctionalInterface</span>'''”</big>注解来标记这个接口。这样做: # 当接口不符合函数式接口定义时(如:无意中增加了另一个抽象方法),编译器会报错; # javaDoc 页中会指示该接口是一个函数式接口; 示例1: <syntaxhighlight lang="java"> Arrays.sort (words , (first , second) -> first.length() - second.length()) ; </syntaxhighlight> # Arrays.sort 方法的第二个参数为 Comparator<String>接口的实现类对象; # 该 lambda表达式 即为 “Comparator<String>”接口的“compare(T, T)”方法的实现; 示例2: <syntaxhighlight lang="java"> Timer t = new Timer(1000, event -> { System.out.println("At the tone, the time is " + new DateO); Toolkit.getDefaultToolkit().beep(); }); </syntaxhighlight> # Timer 构造函数的第二个参数为 ActionListener接口的实现类对象; # 该 lambda表达式 即为 “ActionListener”接口的“actionPerformed(ActionEvent e)”方法的实现; 示例3: <syntaxhighlight lang="java"> list.removelf(e -> e == null); </syntaxhighlight> #(list 对象属于 ArrayList类) # list.removelf 方法(removeIf(Predicate<? super E> filter))的参数为 “Predicate<T>”接口的实现类对象; # 该 lambda表达式 即为 “Predicate<T>”接口的“boolean test(T t)”方法的实现: === 方法引用 === '''利用已有类的方法,来实现函数式接口的抽象方法。'''形式: '''<syntaxhighlight lang="java"> 类或对象::方法 </syntaxhighlight>''' * 函数接口抽象方法的参数列表,必须与 方法引用的参数列表 一致 主要有三种情况: '''<syntaxhighlight lang="xml"> object::instanceMethod Class::staticMethod Class::instanceMethod </syntaxhighlight>''' * 对于前两种,方法引用等价于提供方法参数的 lambda 表达式: *: <syntaxhighlight lang="java" inline>System.out::println</syntaxhighlight> 等价于 <syntaxhighlight lang="java"inline>x -> System.out.println(x);</syntaxhighlight> *: <syntaxhighlight lang="java"inline>Math::pow</syntaxhighlight> 等价于 <syntaxhighlight lang="java"inline>(x, y) -> Math.pow(x, y)</syntaxhighlight> * 对于第三种:第一个参数会成为方法的目标【?即:“para1.instanceMethod(para2)”】: *: <syntaxhighlight lang="java"inline>String::compareToIgnoreCase</syntaxhighlight> 等价于 <syntaxhighlight lang="java"inline>(x, y) -> x.compareToIgnoreCase(y)</syntaxhighlight> 示例: <syntaxhighlight lang="java"> Timer t = new Timer(1000, Systei.out::println); // 等同于 Timer t = new Timer(1000, event -> System.out.println(event)); </syntaxhighlight> <syntaxhighlight lang="java"> Arrays.sort(strings,String::conpareToIgnoreCase); // 等同于 Arrays.sort(strings,(x, y) - > x.compareToIgnore(y)); </syntaxhighlight> === 构造器引用 === 构造器引用 与方法引用类似,只不过方法名为“new”,即: '''<syntaxhighlight lang="java"> 类或对象::new </syntaxhighlight>''' * 可以用数组类型建立构造器引用。 *: 例如,“<syntaxhighlight lang="java" inline>int[]::new</syntaxhighlight>”是一个构造器引用,它有一个参数(即数组的长度)。等价于lambda 表达式“<syntaxhighlight lang="java" inline>x-> new int[x]</syntaxhighlight>”; 示例:(代码中的“stream”得看核心技术卷2) <syntaxhighlight lang="java"> ArrayList<String> names = . . .; Stream<Person> stream = names.stream().map(Person::new); List<Person> people = stream.col1ect(Col1ectors.toList()); </syntaxhighlight> map 方法会为各个列表元素调用 Person(String) 构造器。如果有多个 Person 构造器,编译器会选择有一个 String 参数的构造器, 因为它从上下文推导出这是在对一个字符串调用构造器。 关于Java中创建泛型数组:(泛型是Java的语法糖,在编译时会被替换为实际类型) <pre> Java 有一个限制,无法构造泛型类型 T 的数组。数组构造器引用对于克服这个限制很有用。表达式“new T[n]”会产生错误,因为这会改为“new Object[n]”。 对于开发类库的人来说,这是一个问题。例如, 假设我们需要一个 Person 对象数组。 Stream 接口有一个 toArray 方法可以返回 Object 数组: Object[] people = stream.toArray(); 不过,这并不让人满意。用户希望得到一个 Person 引用数组,而不是 Object 引用数组。流库利用构造器引用解决了这个问题。可以把 Person[]::new 传人 toArray 方法: Person[] people = stream.toArray(Person::new); toArray 方法调用这个构造器来得到一个正确类型的数组。然后填充这个数组并返回。 </pre> === 变量作用域 === * 关于“闭包”:闭包就是能够读取其他函数内部变量的函数。如:Java中的lambda,javascript中函数内部的子函数; lambda 表达式的三个部分: # 一个代码块 # 参数 # '''自由变量的值'''(指非参数,且不在lambda中定义的变量) * 自由变量,能使在 lambda 表达式中访问外围方法或类中的变量: *: <syntaxhighlight lang="java"> public static void repeatMessage(String text, int delay) { ActionListener listener = event -> { System.out.println(text): Toolkit.getDefaultToolkit().beep() : }; new Timer(delay, listener).start(); } </syntaxhighlight> *: 调用 repeatMessage("Hello", 1000); 则会每秒打印一个 Hello; * '''lambda 表达式中捕获的变量必须实际上是最终变量(effectivelyfinal)。''',即这个变量初始化之后就不会再为它赋新值: *: <syntaxhighlight lang="java"> public static void countDown(int start, int delay) { ActionListener listener = event -> { start--; // Error: Can't mutate captured variable System.out.println(start); }; new Timer(delay, listener).start(); } </syntaxhighlight> *: 如上,在 lambda 内改变变量 start 的值,是不合法的; *: <syntaxhighlight lang="java"> public static void repeat(String text , int count) { for (int i = 1; i <= count; i ++) { ActionListener listener = event -> { System.out.print1n(i + ": " + text) ; // Error: Cannot refer to changing i }; new Timer(1000, listener).start(); } } </syntaxhighlight> *: 以上同样不合法:lambda 捕获的变量 i,也不能在外部被改变; * '''lambda 表达式的体与嵌套块有相同的作用域''': *: <syntaxhighlight lang="java"> Path first = Paths.get("usr/Mn"); Couparator<String> comp = (first, second) -> first.length() - second.length(); // Error: Variable first already defined </syntaxhighlight> *: 如上,在lambda中声明与一个局部变量同名的参数或局部变量是不合法的; * '''lambda 中的 this 关键字是指创建这个 lambda 表达式的方法的 this 参数'''; *: <syntaxhighlight lang="java"> public class Application() { public void init() { ActionListener listener = event -> { System.out.println(this.toString()); ... } ... } } </syntaxhighlight> *: 如上,表达式 this.toString() 会调用 Application 对象的 toString 方法,而不是 ActionListener 实例的方法; === 处理lambda表达式 === 使用lambda 表达式的重点是'''延迟执行'''(deferred execution),如以下情况: # 在一个单独的线程中运行代码; # 多次运行代码; # 在算法的适当位置运行代码(例如,排序中的比较操作); # 发生某种情况时执行代码(如,点击了一个按钮,数据到达,等等); # 只在必要时才运行代码; 使用 lambda 表达式,需要选择(偶尔可能需要提供)一个函数式接口: {| class="wikitable" |+ 常用函数式接口 ! 函数式接口 !! 参数类型 !! 返回类型 !! 抽象方法名 !! 描述 !! 其他方法 |- | Runnable || 无 || void || run || 作为无参数或返 || |- | Supplier<T> || 无 || T || get || 提供一个 T 类型的值 || |- | Consumer<T> || T || void || accept || 处理一个 T 类型的值 || andThen |- | BiConsumer<T, U> || T, U || void || accept || 处理 T 和 U 类型的值 || andThen |- | Function<T, R> || T || R || apply || 有一个 T 类型参数的函数 || compose, andThen, identity |- | BiFunction<T, U, R> || T, U || R || apply || 有 T 和 U 类型参数的函数 || andThen |- | UnaryOperator<T> || T || T || apply || 类型 T 上的一元操作符 || compose, andThen, identity |- | BinaryOperator<T> || T,T || T || apply || 类型 T 上的二元操作符 || andThen, maxBy, minBy |- | Predicate<T> || T || boolean || test || 布尔值函数 || and, or, negate, isEqual |- | BiPredicate<T, U> || T, U || boolean || test || 有两个参数的布尔值函数 || and, or, negate |} {| class="wikitable" |+ 基本类型的函数式接口 ! 函数式接口 !! 参数类型 !! 返回类型 !! 抽象方法名 |- | BooleanSupplier || none || boolean || getAsBoolean |- | *Supplier || none || + || getAs* |- | *Consumer || + || void || accept |- | Obj*Consumer<T> || T,+ || void || accept |- | *Function<T> || + || T || apply |- | *To#Function || + || - || applyAs# |- | To*Function<T> || T || + || applyAs* |- | To*BiFunction<T,U> || T, U || + || applyAs* |- | *UnaryOperator || + || + || applyAs* |- | *BinaryOperator || +,+ || + || applyAs* |- | *Pedicate || + || boolean || test |} * “+-” 为 int, long, double; “*,#” 为 Integer, Long, Double === 再谈 Comparator === Comparator 接口包含很多方便的静态方法来创建比较器。这些方法可以用于 lambda 表达式或方法引用。 以下的 comparing()、thenComparing()、nullsFirst()、nullsLast()、naturalOrder()、reverseOrder() 等方法,返回的都是一个'''比较器'''('''Comparator<T>''')。 # 静态 '''comparing''' 方法取一个“键提取器” 函数,它将类型 T 映射为一个可比较的类型(如 String)。对要比较的对象应用这个函数,然后对返回的键完成比较。 #: 例如:假设有一个 Person 对象数组,可以如下按名字对这些对象排序: #: <syntaxhighlight lang="java" highlight=""> Arrays.sort(peop1e, Comparator.comparing(Person::getName)); </syntaxhighlight> # 可以把比较器与 '''thenComparing''' 方法串起来,如果第一次比较相同则使用下一个比较器。 #: 例如: #: <syntaxhighlight lang="java" highlight=""> Arrays.sort(peop1e, Comparator.comparing(Person::getLastName) .thenComparing(Person::ge FirstName)); </syntaxhighlight> # 可以为 comparing 和 thenComparing 方法提取的键指定一个比较器。 #: 例如:可以如下根据人名长度完成排序: #: <syntaxhighlight lang="java" highlight=""> Arrays.sort(peop1e, Comparator.comparing(Person::getName, (s, t) -> Integer.compare(s.length(), t.length()))); </syntaxhighlight> # comparing 和 thenComparing 方法都有变体形式,可以避免 int、long 或 double 值的装箱。 #: 如上例:根据人名长度完成排序,还有一种更容易的做法: #: <syntaxhighlight lang="java" highlight=""> Arrays.sort(people, Camparator.comparingInt (p -> p.getName().1ength())); </syntaxhighlight> # 如果键函数可以返回 null,可能就要用到 '''nullsFirst''' 和 '''nullsLast''' 适配器。这些静态方法会修改现有的比较器,从而在遇到 null 值时不会抛出异常,而是将这个值标记为小于或大于正常值。 #* “nullsFirst”:null < non-null,从小到大时 null 在前; #* “nullsLast”:null > non-null,从小到大时 null 在后; #: 例如,假设一个人没有中名时 getMiddleName 会返回一个 null,就可以使用 #: <syntaxhighlight lang="java" highlight=""> Comparator.comparing(Person::getMiddleName(), Comparator.nullsFirst( ... )) </syntaxhighlight> #* nullsFirst、nullsLast 方法需要一个比较器,在这里就是比较两个字符串的比较器。 # '''naturalOrder''' 方法可以为任何实现了 Comparable 的类建立一个比较器。【自然排序,实现 Comparable 接口时方法作为比较器】 #* 注意 naturalOrder 的类型可以推导得出。 #: 如上例:可以按可能为 null 的中名进行排序,下面是一个完整的调用: #: <syntaxhighlight lang="java" highlight=""> Arrays.sort(peop1e, comparing(Person::getMiddleNarne, nullsFirst(naturalOrder()))); </syntaxhighlight> # 静态 '''reverseOrder''' 方法会提供自然顺序(naturalOrder)的逆序。 #* 要让比较器逆序比较,可以使用 '''reversed''' 实例方法。 #*: 例如:<syntaxhighlight lang="java" inline>naturalOrder().reversed()</syntaxhighlight> 等同于 <syntaxhighlight lang="java" inline>reverseOrder()</syntaxhighlight> 。 == 内部类 == 内部类( inner class ) 是'''定义在另一个类中的类'''。<br/> 使用内部类主要原因: # 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据。 # 内部类可以对同一个包中的其他类隐藏起来。 # 想要定义一个回调函数且不想编写大量代码时,使用匿名(anonymous) 内部类比较便捷。 * 只有内部类可以是私有类(“private class xxx{}”),而常规类只可以具有包可见性(“class xxx{}”),或公有可见性(“public class xxx{}”); === 内部类访问外围类的实例域 === 内部类的对象总有一个隐式引用,它指向了创建它的外部类对象。 * 通过这个对象,内部类既可以访问自身的数据域,也可以访问创建它的外围类的数据域; * 但是除了局部内部类,其他内部类不能访问方法的局部变量; [[File:内部类对象的外围类对象引用.png|600px]] === 内部类的特殊语法规则 === 内部类的使用: * 内部类中的外围类引用为:“'''outerClass.this'''”(outerClass为外围类类名)。 * 在外围类中可以这样构造内部类对象:“'''outerObject.new InnerClass{construction parameters)'''”(this可以省略) *: 对于公有内部类,任意的外部类对象都可以构造一个内部类; *: <syntaxhighlight lang="java"> TalkingClock jabberer = new Ta1kingClock(1000, true) ; TalkingClock.TiiePrinter listener = jabberer.new TimePrinter(); </syntaxhighlight> * 在外围类的作用域中,可以用“'''OuterClass.InnerClass'''”来引用内部类; <syntaxhighlight lang="java"> public class TalkingClock { private int interval: private boolean beep; public TalkingClock(int interval , boolean beep) { this,interval = interval; this.beep = beep; } public void start() { ActionListener listener = new TimePrinter(); // 或 ActionListener listener = this.new TimePrinter(); Timer t = new Timer(interval , listener); t.start(); } public class TimePrinter implements ActionListener { public void actionPeformed(ActionEvent event) { System.out.println("At the tone, the time is " + new Date()); // 访问外部类的域 if (beep) Toolkit.getDefaultToolki1().beep(); // 使用外部类引用 if (TalkingClock.this,beep) Toolkit.getDefaultToolkitO.beep(); } } } </syntaxhighlight> 内部类的规则:【???说的啥都是】 * 内部类中声明的所有静态域都必须是 final(即常量); *: 如果不为final,则由于每个外部实例都有一个内部类实例,那么静态域可能不唯一(静态域的意义就是多个实例共享一个值) * 内部类不能有静态方法(除了静态内部类); * '''非static的内部类,在外部类加载的时候,并不会加载它,所以它里面不能有静态变量或者静态方法'''。 *: 因为“static类型的属性和方法,在类加载的时候就会存在于内存中”,“要使用某个类的static属性或者方法,那么这个类必须要加载到jvm中”,如果一个非static的内部类如果具有static的属性或者方法,那么就会出现:内部类未加载(非静态内部类在外围类实例构建之后加载?),但是却试图在内存中创建static的属性和方法,这当然是错误的。 === 内部类的类文件 === * 内部类是一种编译器现象, 与虚拟机无关。编译器将会把内部类翻译成用“$”分隔外部类名与内部类名的常规类文件。 *: 如:TalkingClock类内部的TimePrinter类的类文件为“TalkingClock$TimePrinter.class” <syntaxhighlight lang="java"> public class TalkingClock$TimePrinter { public TalkingGock$TimePrinter(TalkingCtock); // 构造函数:用外围类构造内部类 public void actionPerformed(java.awt.event.ActionEvent); // 内部类方法 final TalkingClock this$O; // 外围类引用 } </syntaxhighlight> === 局部内部类 === 局部内部类:定义在方法中的内部类: * 局部内部类不用 public、private 进行声明; * 局部内部类的作用域限定在所在代码块中; *: 对代码块外完全隐藏; <syntaxhighlight lang="java"> public void start() { class TimePrinter implements ActionListener { public void actionPerforaed(ActionEvent event) { Systei.out.println("At the tone, the tine is " + new DateO); if (beep) Toolkit.getDefaul tToolki10•beep(): } } ActionListener listener = new TimePrinter(); Timer t = new Timer(interva1, listener); t.start(); } </syntaxhighlight> * '''访问外部方法局部变量''':与其他内部类相比较,局部类还有一个优点:它们不仅能够访问包含它们的外部类,还可以访问局部变量: *: 编译器会为局部内部类构造具有参数的构造函数和域,用于保存外围方法的局部变量: *: <syntaxhighlight lang="java"> class TalkingClock$ITimePrinter { TalkingClock$ITinePrinter(TalkingClock, boolean); // 以外围方法局部变量作为参数的构造函数 public void actionPerformed(java.awt.event.ActionEvent); // final boolean val$beep; // 变量:用于存储外围方法的局部变量 final TalkingClock this$O; // 外围类引用 } </syntaxhighlight> === 匿名内部类 === 匿名内部类(anonymous inner class):没有类名,只用于构造一个该类的实例。形式为: * 由于构造器的名字必须与类名相同,而匿名内部类没有类名,所以,'''匿名内部类不能有构造器'''。 *# 继承超类的匿名内部类:将构造器参数传给超类构造器; *#: <syntaxhighlight lang="java"> new SuperType(construction parameters) { inner class methods and data } </syntaxhighlight> *# '''实现接口的匿名内部类:不能有构造参数'''。 *#: <syntaxhighlight lang="java"> new InterfaceType() { methods and data } </syntaxhighlight> 如下,创建一个了实现ActionListener 接口的类的新对象,需要实现的方法 actionPerformed 定义在括号内。 <syntaxhighlight lang="java"> public void start(int interval, boolean beep) { ActionListener listener = new ActionListener() { public void actionPerformed(ActionEvent event) { System.out.println("At the tone, the time is " + new Date()); if (beep) Toolkit.getDefaultToolkit().beep(); } }; Timer t = new Timer(interval, listener); t.start(); } </syntaxhighlight> 使用匿名内部类与实例化对象: <syntaxhighlight lang="java"> Person queen = new Person("Mary"); // 这是在构造对象 Person count = new Person("Dracula") { ... } // 这是在使用匿名内部类 </syntaxhighlight> ==== 双括号初始化 ==== 双括号初始化(double brace initialization): <syntaxhighlight lang="java"> ArrayList<String> friends = new ArrayListoO; friends,add("Harry"); friends,add("Tony") ; invite(friends) ; </syntaxhighlight> 可以被替换为: <syntaxhighlight lang="java"> invite(new ArrayList<String>0 {{ add("Harry") ; add("Tony") ; }}) ; </syntaxhighlight> # 外部大括号用于建立一个ArrayList<String>的匿名内部类; # 内层大括号则是一个初始化块(见[http://wiki.eijux.com/%E6%A0%B8%E5%BF%83%E6%8A%80%E6%9C%AF%EF%BC%9A%E5%AF%B9%E8%B1%A1%E4%B8%8E%E7%B1%BB#.E5.88.9D.E5.A7.8B.E5.8C.96.E5.9D.97]); === 静态内部类 === 有时候,使用内部类只是为了把一个类隐藏在另外一个类的内部,'''并不需要内部类引用外围类对象'''。为此,可以将内部类声明为static, 以便取消产生的引用。 * 只有内部类可以被声明为 static * 没有外围类的对象的引用 * 可以有静态域和静态方法(其他内部类不可以) == 代理 == 参考:<big>'''[[动态代理、CGLIB 与 切面编程]]'''</big> 如果要为一个没有实现接口的类生成动态代理类,那么可以使用 '''CGLIB''' 库。 代理(Proxy):利用代理可以在'''运行时,创建一个实现了一组给定接口的新类'''。(动态代理) *(静态代理:通过聚合、组合的方式,对需要的类或接口进行前后处理,提供新的调用) * 代理类可以在运行时创建全新的类; * 只有在编译时无法确定需要实现那个接口时才有必要使用。 === 创建一个代理对象 === 要想创建一个代理对象,需要使用'''Proxy类'''的'''newProxylnstance'''方法。这个方法有三个参数: # 类加载器(“ClassLoader”):用 null 表示使用默认的类加载器;(有关类加载器,查阅:卷2第9章) # 接口数组(“Class[]”):一个Class对象数组,每个元素都是需要实现的接口。 # 调用处理器(“InvocationHandler”):定义对代理类方法的处理。 * 调用处理器需要实现 InvocationHandler 接口:仅有一个方法 '''invoke''',用于对代理实例的方法进行处理: *: <syntaxhighlight lang="Java" highlight=""> Object invoke(Object proxy, Method method, Object[] args) </syntaxhighlight> * 代理类属于在运行时定义的类(名字如“'''$Proxy0'''”),这个类实现了给定的接口(如“Comparable”),它的方法(如“compareTo”)调用了代理对象处理器的“invoke”方法。 ==== 示例 ==== ProxyTest: <syntaxhighlight lang="java"> package Proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.Random; public class ProxyTest { public static void main(String[] args) { Object[] elements = new Object[1000]; // fill elements with proxies for the integers 1 ... 1000 for (int i = 0; i < elements.length; i++) { Integer value = i + 1; InvocationHandler handler = new TraceHandler(value); // 调用 Proxy.newProxyInstance // 1、构造实现指定接口(“Comparable”)的代理类的一个新实例 // 2、所有方法会调用给定处理器对象(“handler”)的 invoke 方法 Object proxy = Proxy.newProxyInstance(null, new Class[] { Comparable.class }, handler); // elements的每个元素都是一个代理类实例 elements[i] = proxy; } // elements的元素类型为:“class com.sun.proxy.$Proxy0” //System.out.println(elements[1].getClass()); // construct a random integer // 生成随机数key Integer key = new Random().nextInt(elements.length) + 1; // search for the key // 在elements中使用二分查找key,使用的方法是“binarySearch(Object[] a, Object key)” // binarySearch方法会用到,代理类实例所(即每个elements元素)实现的Comparable接口下compareTo方法 // 而代理类实例的每个方法都会调用 “TraceHandler”的“invoke”(打印内容) /* 打印的内容如: * 985.compareTo(973) * 977.compareTo(973) * 973.compareTo(973) */ int result = Arrays.binarySearch(elements, key); // print match if found if (result >= 0) // 调用了代理类实例的“toString()” // 而该“toString()”依然会调用 “TraceHandler”的“invoke” /* 打印的内容如:1、先调用了 “TraceHandler”的“invoke”,2、toString打印了代理实例的 * 973.toString() * 973 */ System.out.println(elements[result]); } } </syntaxhighlight> TraceHandler: <syntaxhighlight lang="java"> package Proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class TraceHandler implements InvocationHandler { private Object target; /** * Constructs a TraceHandler * * @param t * the implicit parameter of the method call */ public TraceHandler(Object t) { target = t; } // (所有代理类实例的方法都会调用这个方法) // 其中定义了代理对象调用方法时希望执行的动作:打印“target.调用的方法(参数列表)” public Object invoke(Object proxy, Method m, Object[] args) throws Throwable { // print implicit argument System.out.print(target); // print method name System.out.print("." + m.getName() + "("); // print explicit arguments 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(") "); // invoke actual method // 这次调用实例的方法 // 说明可以在这里进行前处理、后处理 return m.invoke(target, args); } } </syntaxhighlight> === 代理类的特性 === 代理类是在程序运行过程中创建的。然而,一旦被创建,就变成了常规类,与虚拟机中的任何其他类没有什么区别。<br/> * '''所有的代理类都扩展于Proxy 类'''。 * 一个代理类只有一个实例域(调用处理器),它定义在Proxy 的超类中。 * 代理类所需要的任何附加数据都必须存储在调用处理器中。 * 所有的代理类都覆盖了Object 类中的方法 toString、equals 和hashCode。如同所有的代理方法一样, 这些方法仅仅调用了调用处理器的invoke。 *: Object 类中的其他方法(如 clone 和 getClass ) 没有被重新定义。 * 没有定义代理类的名字,Sun 虚拟机中的Proxy 类将生成一个以字符串SProxy 开头的类名。 * 对于特定的类加载器和预设的一组接口来说,只能有一个代理类。 *: 也就是说,如果使用同一个类加载器和接口数组调用两次newProxylustance 方法的话, 那么只能够得到同一个类的两个对象,也可以利用getProxyClass 方法获得这个类: *: “Class proxyClass = Proxy.getProxyClass(null, interfaces);” * 代理类一定是public 和final。 * 如果代理类实现的所有接口都是public,代理类就不属于某个特定的包;否则, 所有非公有的接口都必须属于同一个包,同时,代理类也属于这个包。 * 可以通过调用Proxy 类中的isProxyClass 方法检测一个特定的Class 对象是否代表一个代理类。 ==== java.Iang.reflect.InvocationHandler ==== # Object invoke(Object proxy,Method method,0bject[]args) #: 定义了代理对象调用方法时希望执行的动作。【对代理类对象的方法进行处理】 ==== java.Iang.reflect.Proxy ==== # static Class<?> getProxyClass(Cl assLoader loader, Class<?>...interfaces) #: 返回实现指定接口的代理类。【返回代理类】 # static Object newProxyInstance(ClassLoader loader, Class<?>[]interfaces, InvocationHandler handler) #: 构造实现指定接口的代理类的一个新实例。【返回代理类的实例】 #: 所有方法会调用给定处理器对象的invoke 方法。 # static boolean isProxyClass(Class<?> cl) #: 如果cl 是一个代理类则返回true。
返回至“
核心技术:接口、lambda表达式与内部类
”。
导航菜单
个人工具
登录
命名空间
页面
讨论
大陆简体
已展开
已折叠
查看
阅读
查看源代码
查看历史
更多
已展开
已折叠
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
笔记
服务器
数据库
后端
前端
工具
《To do list》
日常
阅读
电影
摄影
其他
Software
Windows
WIKIOE
所有分类
所有页面
侧边栏
站点日志
工具
链入页面
相关更改
特殊页面
页面信息