目录
一.接口
1.接口基本定义
2.接口的特性
二.接口实例
1.接口与回调
2.Comparator接口
3.对象克隆
三.lambda表达式
1.基础语法
2.函数式接口
3.构造器引用
4.变量作用域(Java闭包)
5.处理lambda表达式
四.内部类
1.内部类定义
2.使用内部类访问对象状态
3.局部内部类
4.匿名内部类
5.静态内部类
五.代理
一.接口
1.接口基本定义
所谓接口就是某个类的一些方法的声明集合, 用于整理这个类的一些需要用的方法, 我感觉类似于C++中的.h头文件, 在接口中只做一些声明而不做具体的定义, 具体的定义放在类的实现中;
比如Comparable接口, 这是一个泛型接口, 例如:
public interface Comparable<T>{
int compareTo(T other);// parameter has type T
}
这就意味着, 所有支持Comparable这个接口的类, 一定要包含有compareTo方法, 输入是泛型(有的接口会规定具体的类型), 返回值是int型, 而且所有的接口中的具体方法在实现的时候都必须是public, 而且也不能省略, 否则会因为privacy问题被编译器报错;
下面上一个我自己写的用前面的RichPeople类改写的接口实现对RichPeople类对象进行按Money的排序:
import java.util.*;
public class test6 {
public static void main(String[] args) {
People[] staff = new People[3];
staff[0] = new RichPeople("Amos",20,100,1000);
staff[1] = new RichPeople("Abis",20,100,10);
staff[2] = new RichPeople("Adam",20,100,10);
for(Object i : staff){System.out.println(i);}
System.out.println();
Arrays.sort(staff);
for(Object i : staff){System.out.println(i);}
}
}
abstract class Person{
...
}
class People extends Person{
...
}
class RichPeople extends People implements Comparable<RichPeople>{
...
public int compareTo(RichPeople u){
return Integer.compare(this.getMoney(), u.getMoney());
}
}
我省略掉了一些不太重要的前面出现过的代码, 重点关注这一部分:
class RichPeople extends People implements Comparable<RichPeople>{
...
public int compareTo(RichPeople u){
return Integer.compare(this.getMoney(), u.getMoney());
}
我实现了一个支持Comparable接口的RichPeople类, 同时我重写了compareTo方法, 如果输入对象的Money大于this的Money则会返回一个负数, 如果输入对象的Money小于this的Money则会返回一个整数, 如果相等则会返回0;
所以上面的函数的输出结果如下:
RichPeople[Name:Amos,Age20,Money:1100]
RichPeople[Name:Adis,Age20,Money:110]
RichPeople[Name:Abam,Age20,Money:110]
RichPeople[Name:Adis,Age20,Money:110]
RichPeople[Name:Abam,Age20,Money:110]
RichPeople[Name:Amos,Age20,Money:1100]
2.接口的 特性
接口的定义方式大概长这样:
public interface zilean{
int time=10;
void timeprint();
}
在接口内部可以定义域, 所有的域会被自动修饰成public static final;
(如果把类和接口放在同一个文件中, 要把接口或者类的public修饰符去掉一个, 即同一个文件中只能存在一个public的修饰符修饰的类或者枚举或者接口等等)
接口也是可以被继承的,像下面这样写:
interface zileanson extends zilean {
int time=11;
void timeprint();
}
可以用接口名来声明某一个变量, 但是不能直接new一个接口类型, 但是可以把这个变量指向一个对象, 这个对象必须要是支持这个接口的;
可以用instanceof来判断某个变量是否是属于某个接口的对象;
我自己的测试代码如下:
import java.util.*;
import java.time.*;
public class test6 {
public static void main(String[] args) {
zilean a = new people("zilean", 21);
people x = (people)a;
x.timeprinto();
x.tt();
zilean.ti();
}
}
class people implements zilean, zileanson{
private String name = "";
private int age = 0;
public people(String name, int age){
this.name = name;
this.age = age;
}
public people(){}
public String getName(){
return this.name;
}
public void timeprint(){
System.out.println(zilean.time);
}
public void timeprinto(){
System.out.println(this.name+" changed the time to "+LocalDate.now()+"\n");
}
}
interface zilean{
int time=10;
void timeprint();
public static void ti(){
System.out.println("ti");
}
default void tt(){System.out.println("tttttt");}
}
interface zileanson extends zilean {
int time=11;
void timeprint();
}
对于java中接口的一些功能, 其实也可以放在类的超类(抽象类)中写, 但是因为java不支持多继承, 所以不能继承多个抽象类, 但是一个类可以支持多个接口, 所以接口基本可以提供多继承的很多好处, 在一些支持多继承的语言中, 比如C++就可以使用多继承来实现接口的一些特性;
在接口中可以写静态方法,调用的时候可以直接zilean.ti();
但是要记得用public static修饰;
在接口中可以为一个写默认方法, 用default修饰就可以, 可以写一个默认的方法实现, 虽然规定要在类的内部重写接口中的每个方法, 但是如果写了default的话还是会比较方便;
关于默认方法可能会带来的一些问题:
如果先在一个接口中将一个方法定义为默认方法, 然后又在超类或另一个接口中定义了同样的方法,那么就会产生冲突,;
Java对此的解决方法是: 超类方法优先;
但是, 当两个接口中提供了同名方法而且都写了默认方法时, 那么会产生冲突, 编译器会报错;
即使两个接口中提供了同名方法但是只有一个写了默认方法, 编译器也会报错, 而不会自动选择默认方法;
可以这样来选择一个方法的实现方法:
class student implements named,person{
public getName(){
return named.getName();
}
}
正因为java这样的冲突处理方式是超类继承优先的, 所以不要试图用默认方法在接口中重写在Object类中的一些方法, 比如toString方法和equals方法, 因为这样肯定是会被Object的方法覆盖掉的;
二.接口示例
1. 接口与回调(callback)
import java.awt.event.*;
import java.util.*;
import javax.swing.Timer;
import javax.swing.*;
import java.awt.*;
class test7 {
public static void main(String[] args) {
ActionListener ac = new act();
javax.swing.Timer timeac = new Timer(3000, ac);
timeac.start();
JOptionPane.showMessageDialog(null, "quit program?");
System.exit(0);
}
}
class act implements ActionListener{
public void actionPerformed(ActionEvent event){
System.out.println("now time is : "+new Date());
Toolkit.getDefaultToolkit().beep();
}
}
回调指的是在某个特定事件发生后会采取特定的某些操作, 比如上面的程序就是在计时器每3000ms会打印出一行当前时间, 并且调用系统发出通告声音beep();
在这里不需要对代码的具体内容和实现太关注, 只需要关注这种回调的设计模式就可以了;
上面的代码中定义了一个支持ActionListener的act类, 在其中重写了actionPerformed方法, 每当发生一个event事件都会打印出一条通告信息并且执行beep();
2.Comparator接口
我们可以自己实现一个支持Comparator比较器接口的类, 比如下面的程序可以把一个字符串数组按字符串的长度排序:
import java.util.*;
public class test8{
public static void main(String[] args) {
String[] s = {"John", "Samira", "Mustafa", "Adam", "Recluse"};
for(String i : s) System.out.println(i);
Arrays.sort(s, new lengthcompare());
System.out.println();
for(String i : s) System.out.println(i);
}
}
class lengthcompare implements Comparator<String>{
public int compare(String a, String b){
return a.length()-b.length();
}
}
我们在lengthcompare类中对Comparator接口进行继承, 自己实现了compare方法, 返回值是a.length()-b.length();
以实现按字符串长度比较进行排序的函数;
在调用比较器的时候可以直接传入一个支持Comparator接口的类作为比较器;
3.对象克隆
如果我们想把一个对象拷贝的话, 要是直接使用=, 其结果会直接把新声明的变量指向原先的对象, 比如:
People a = new People("Amos", 21);
People b = a;
这样b其实只是对a的一个引用, 所有对b的值进行改变的操作都会改变a内部的值, 如果希望能独立的拷贝一个与a完全相同的对象, 但是因为clone方法是Object方法的一个方法, 虽然是protected的方法, 但是并不能直接调用clone方法, 要在类内部重新把clone定义为public修饰的方法才能直接调用, 同时, 还要注意捕获异常error: unreported exception CloneNotSupportedException; must be caught or declared to be thrown
比如可以使用try catch进行异常捕获:
class People implements Cloneable {
public static void main(String[] args){
People a = new People("Amos", 21);
System.out.println(a.getAge());
People b = (People)a.clone();
b.AddAge(10);
System.out.println(a.getAge());
System.out.println(b.getAge());
}
...
public People clone(){
try {
return (People)super.clone();
}
catch (Exception CloneNotSupportedException) {
}
return null;
}
}
因为在类中如果有某个变量引用了某一个其他变量, 那么在克隆过程中就会出现一个引用了和原对象相同变量的变量, 这样会比较不安全, 所以要在修正克隆方法的同时对这些不安全性进行修正, 比如新建立一个对象, 作为引用传给克隆的对象;
三.lambda表达式
1.基础语法
基础的语法大概是这样的:
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
一个看起来不错的方法是, 可以用lambda表达式来做比较器, 比如之前写的按字符串长度来给字符串数组排序的程序就可以这么写:
class Untitled {
public static void main(String[] args) {
String[] s = {"Mustafa", "Samira", "Akhil"};
for(String i : s) System.out.println(i);
Arrays.sort(s, (first, second)->first.length()-second.length());
for(String i : s) System.out.println(i);
}
}
看起来似乎lambda表达式就是一个语法糖…
2.函数式接口
有的方法是可以接受一个函数式的接口的, 可以传入一个lambda表达式作为一个函数式接口:
可以使用lambda的地方也可以使用简单的方法引用,比如下面两个语句是等价的:
Timer ti = new Timer(2000, event->System.out.println(event) );
Timer ti = new Timer(2000, System.out::println );
也可以像这样:
Timer ti = new Timer(2000, event->System.out.println(new Date()) );
讲道理的话也可以这样来调用父类中的方法引用, 但是不知道为啥我这样做的时候会报错, 报错信息我贴在下面了, 希望大手子能教我一下这样写为什么不对呀QAQ:
Timer ti = new Timer(2000, super::greet() );
//error: constructor Timer in class Timer cannot be applied to given types;
// Timer ti = new Timer(2000, super::greet);
// ^
// required: int,ActionListener
// found: int,super::greet
// reason: argument mismatch; invalid method reference
// method greet in class father cannot be applied to given types
// required: no arguments
// found: ActionEvent
// reason: actual and formal argument lists differ in length
//1 error
明明书上这么写就可以用嘛, 嘤嘤嘤!
3.构造器引用
构造器算是一个特殊的方法, 也是可以被作为方法引用传入的, 比如对于Person类我们可以使用Person::new来获得该类的构造器引用, 也可以
Person[] p = stream.toArray(Person[]::new);
有可能有多个构造器的情况下, 具体获得哪个构造器要看具体传入的参数类型:
4.变量作用域(Java闭包)
在java的lambda表达式内部, 可以使用当前函数内的某些变量, 但是使用的必须是final或者不会改变的变量(must be final or effectively final ), 同时, 也不能使用和当前函数内部相同的变量名;
之所以有这样的限制的原因在于, lambda表达式并不是在调用的时候立即执行的, 而是延迟执行(deferred execution ), 所以如果在lambda中使用了外部的局部变量, 可能会获得这个变量不确定时候的值, 这样是很危险的, 所以编译器不允许这么做;
5.处理lambda表达式
真滴没看懂这啥玩意儿, 以后再说吧…
为什么要使用lambda呢,因为:
四.内部类
1.内部类定义
为什么需要内部类呢?CoreJava上给出的原因如下:
不知道你有没有看懂, 反正我看完感觉是个鸡肋…
于是我就去网上查了一下内部类到底有啥用:
CSDN的图床上传不了, 心痛…推荐一手微博图床还不错嘻嘻
2.使用内部类访问对象状态
一个方法可以引用调用这个方法的对象数据域, 内部类既可以访问自身的数据域, 也可以访问创建它的外围类对象的数据域.
上一段内部类的测试代码:
import java.awt.event.*;
import java.awt.*;
import java.util.*;
import javax.swing.Timer;
class test10{
public static void main(String[] args) {
test10 me = new test10(1000,true);
test10.talk you = me.new talk();
me.begin();
while(true){}
}
int interval = 1000;
int a = 2010;
boolean beep = true;
public test10(int interval, boolean beep){
this.interval = interval;
this.beep = beep;
}
public void begin(){
ActionListener listener = new talk();
Timer ti = new Timer(interval, listener);
ti.start();
}
public class talk implements ActionListener{
private static final int a = 2018;
@Override
public void actionPerformed(ActionEvent event){
System.out.println(a);
if(test10.this.beep) Toolkit.getDefaultToolkit().beep();
}
}
}
在上面的例子中, 我们来分析一下内部类是如何使用的:
1.首先, 我们希望定义一个支持ActionListener接口的内部类用于实现打印和蜂鸣器的作用, 要注意在内部类的内部定义中, 不可以使用static方法, 而且所有定义为static的变量都必须是final类型, 否则编译器会报错;
为什么所有的static静态域都必须是final的呢?
因为对于每一个外部类, 都会有一个内部类的实例域, 而如果一个内部类的域是静态的,那也就意味着它可以独立存在, 甚至看做是外部类的一部分, 是被所有的对象所共享的, 如果被某个对象更改了取值, 就可能会影响所有对象的域取值
2.然后是变量作用域的问题:
如果直接使用某个变量, 如果内部类内部定义了这个变量, 则会优先使用这个变量, 如果内部类没有定义这个变量但是在外部的类中定义了, 就会使用外部类中的这个变量
3.在创建外部类对象的时候, 内部类对象不会自动创建, 需要自己手动创建, 比如像我上面这样:
test10 me = new test10(1000,true);
test10.talk you = me.new talk();
需要用me.new来新建一个内部类;
3.局部内部类
可以把内部类写在一个方法里面, 这样这个内部类只有对当前的方法内可见(作用于限于该方法局部, 对外部不可见, 实现封装性);
局部内部类不仅可以访问他们的外部类, 还可以访问局部方法的属性…但是这些局部变量必须是final的;
感觉没啥用, 就这样吧嘻嘻
4.匿名内部类
可以这样写一些只用一次的内部类, 可能会比较方便吧…
据说曾经的程序员是经常使用匿名内部类的
但是由于现在java8有的新特性的lambda表达式所以最好的办法还是使用lambda表达式(不过如果是有多个方法的话, lambda表达式应该还是不能胜任把:D 喷一波lambda很皮 )
还有一个利用匿名内部类的技巧:
5.静态内部类
好像我的理解还有点问题…后面再补
这几个部分写的水多了哈哈, 因为在知乎上看到一句话说不要对语言的细节太在意:D真有道理 开心多了
五.代理
没看懂, 妈的, 心态崩了, 呜呜呜
啥破玩意儿;
结束, 嘤嘤嘤, 耻辱结尾