0x00 本系列-Java集训系列
记录我带领小组进行Java学习,按照《Java核心技术》第十版进行学习
0x01 接口、lambda、内部类
0x02
接口(Interface)是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义
可以将接口看成没有属性的抽象类(看成的意思就是两者有一些区别,但是大体上是差不多的)
定义接口:public interface interfacename{}
实现接口:public class classname implements interfacename1,interfacename2{}
可以看到接口与抽象类很像,不过有个最大的不同就是一个类可以实现任意个接口,而一个类只能继承一个抽象类,这样就导致了使用方法上有很大不同
在接口中定义的方法,实现接口的类必须实现,除非加default 描述符,在定义方法的时候将此关键字加到前面,并且在接口中实现一个默认方法,就可以在实现接口的类中不实现此方法,使用默认的方法(有点绕,看代码)
如果先在一个接口中将一个方法定义为默认方法,然后又在超类或另一个接口中定义了同样的方法,就会产生冲突,以下是Java在解决冲突方面的规则
1)超类优先。如果超类提供了一个具体方法,同名而且具有相同参数类型的默认方法会被忽略。
2)接口冲突。如果一个超接口提供了一个默认方法,另一个接口提供了一个同名而且参数类型相同的方法,必须覆盖这个方法来解决冲突
如果一个类实现的两个接口中定义了同样的方法,并且至少其中一个是**默认(default)**方法,则实现类必须要覆盖方法,选择手动选择其中一个来调用
接口的定义和实现
InterfaceTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package cc.singll.learnjava.interfaces;public class InterfaceTest { public static void main (String[] args) { Customizee<Person> persona = new Person ("Tony" , 22 , 1 ); Person personb = new Person ("Tom" , 25 , 0 ); boolean agtb = persona.greaterThan(personb); System.out.println("a gt b ?" + agtb); int reta = persona.ret(); int retb = personb.ret(); System.out.println("reta = " + reta + ", retb = " + retb); } }
Person.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package cc.singll.learnjava.interfaces;public class Person implements Customizee <Person> { private String name; private int age; private int sex; public Person (String name, int age, int sex) { this .name = name; this .age = age > 0 ? age:0 ; this .sex = sex == 0 ? 0 :1 ; } public String getName () { return name; } public int getAge () { return age; } public String getSex () { return age==0 ?"男" :"女" ; } public boolean greaterThan (Person a) { return this .age+C_INT>a.age+C_INT?true :false ; } }
Customizee.java
1 2 3 4 5 6 7 8 9 package cc.singll.learnjava.interfaces;public interface Customizee <T> extends Customize <T> { default int ret () {return 0 ;} }
Customize.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package cc.singll.learnjava.interfaces;public interface Customize <T> { double C_INT = 20 ; boolean greaterThan (T a) ; }
书中实现基础接口和调用的示例(就不细标示出了)
EmployeeSortTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package cc.singll.learnjava.interfaces;import java.util.*;public class EmployeeSortTest { public static void main (String[] args) { Employee[] staff = new Employee [3 ]; staff[0 ] = new Employee ("Harry Hacker" , 35000 ); staff[1 ] = new Employee ("Carl Cracker" , 75000 ); staff[2 ] = new Employee ("Tony Tester" , 38000 ); Arrays.sort(staff); for (Employee e : staff) System.out.println("name=" + e.getName() + ",salary=" + e.getSalary()); } }
Employee.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package cc.singll.learnjava.interfaces;public class Employee implements Comparable <Employee> { private String name; private double salary; public Employee (String name, double salary) { this .name = name; this .salary = salary; } public String getName () { return name; } public double getSalary () { return salary; } public void raiseSalary (double byPercent) { double raise = salary * byPercent / 100 ; salary += raise; } public int compareTo (Employee other) { return Double.compare(salary, other.salary); } }
回调是一种常见的程序设计模式,指出某个特定事件发生时应该采用的动作。
简单理解就是延迟调用,普通的代码都是顺序的,执行到就执行了,回调就是先将定义好的代码放到某个位置,然后等到满足条件的时候就调用那部分代码。
TimerTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package cc.singll.learnjava.timer;import java.awt.*;import java.awt.event.*;import java.util.*;import javax.swing.*;import javax.swing.Timer;public class TimerTest { public static void main (String[] args) { ActionListener listener = new TimePrinter (); Timer t = new Timer (10000 , listener); t.start(); JOptionPane.showMessageDialog(null , "Quit program?" ); System.exit(0 ); } }class TimePrinter implements ActionListener { public void actionPerformed (ActionEvent event) { System.out.println("At the tone, the time is " + new Date ()); Toolkit.getDefaultToolkit().beep(); } }
克隆,clone是Object的一个protected方法,所以自定义的类想要克隆就要覆盖clone方法并将修饰符变成public
Object的clone方法默认是”浅拷贝”,即如果属性是可变的(比如各种对象类型),则只拷贝引用,不拷贝一份完整的对象
想要进行”深拷贝”就要自己实现
CloneTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package cc.singll.learnjava.clone;public class CloneTest { public static void main (String[] args) { try { Employee original = new Employee ("John Q. Public" , 50000 ); original.setHireDay(2000 , 1 , 1 ); Employee copy = original.clone(); copy.raiseSalary(10 ); copy.setHireDay(2002 , 12 , 31 ); System.out.println("original=" + original); System.out.println("copy=" + copy); } catch (CloneNotSupportedException e) { e.printStackTrace(); } } }
Employee.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 package cc.singll.learnjava.clone;import java.util.Date;import java.util.GregorianCalendar;public class Employee implements Cloneable { private String name; private double salary; private Date hireDay; public Employee (String name, double salary) { this .name = name; this .salary = salary; hireDay = new Date (); } public Employee clone () throws CloneNotSupportedException { Employee cloned = (Employee) super .clone(); cloned.hireDay = (Date) hireDay.clone(); return cloned; } public void setHireDay (int year, int month, int day) { Date newHireDay = new GregorianCalendar (year, month - 1 , day).getTime(); hireDay.setTime(newHireDay.getTime()); } public void raiseSalary (double byPercent) { double raise = salary * byPercent / 100 ; salary += raise; } public String toString () { return "Employee[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay + "]" ; } }
lambda表达式是一个可传递的代码块。
首先是什么地方需要使用lambda表达式,在需要回调的地方,并且回调需要的代码只执行一两次,就可以用lambda,而不是用上例那样再定义一个类,很繁琐。
然后,lambda表达式语法:
完整形式:(type name1,type name2)->{ xxxxx;return xx;}
首先是一个括号,括号里是标准的参数类型+参数名称,然后一个->,最后一个代码块,代码块里要有返回值
省略形式:(name1,name2)->{xxxx;return xx;}//如果参数类型可以推导出来,那么就可以省略
省略形式:()->{xxx;return xx;}//如果没有参数就空的括号
省略形式:name->{xxx;return xx;}//如果只有一个参数,可以省略括号和类型
省略形式:()->xxx; //如果代码只有一行,并且是返回值,可以不用return,代码执行结果就当作返回值
如果代码块里由分支,那么其中一个有返回值,另一个必须有返回值
LambdaTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package cc.singll.learnjava.lambda;import java.util.*;import javax.swing.*;import javax.swing.Timer;public class LambdaTest { public static void main (String[] args) { String[] planets = new String [] {"Mercury" , "Venus" , "Earth" , "Mars" , "Jupiter" , "Saturn" , "Uranus" , "Neptune" }; System.out.println(Arrays.toString(planets)); System.out.println("Sorted in dictionary order:" ); Arrays.sort(planets); System.out.println(Arrays.toString(planets)); System.out.println("Sorted by length:" ); Arrays.sort(planets, (first, second)->first.length()-second.length()); System.out.println(Arrays.toString(planets)); Timer t = new Timer (1000 , event->System.out.println("The time is " + new Date ())); t.start(); JOptionPane.showMessageDialog(null , "Quitprogram?" ); System.exit(0 ); } }
方法引用是一个语法糖,即编译器进行优化的语法。
方法引用等价于lambda表达式,比如System.out::println 等价于 x->System.out.println(x)
方法引用有如下三种形式
object ::instanceMethod
Class ::staticMethod
Class ::instanceMethod
前两种情况,方法引用等价于提供方法参数的lambda表达式。
第一种:System.out.println 等价于 x->System.out.println(x)
第二种:Math::pow 等价于 (x,y)->Math.pow(x,y)
第三种:String::compareToIgnoreCase 等价于 (x,y)->x.compareToIgnoreCase(y)
this::equals 等价于 x->this.equals(x)
super ::instanceMethod 也一样
构造器引用:Class ::new
关于变量作用域,看书就好了,多看看才能理解
主要问题在于,如果引用外部变量,是有一定规则的。
lambda表达式使用,lambda表达式的特点是延迟执行,而不是立刻运行,因为一般的代码都是立刻运行的,也不需要用lambda,那么用lambda表的是的需求原因典型有如下:
在一个单独的线程中运行代码;
多次运行代码;
在算法的适当位置运行代码(例如,排序中的比较操作);
发生某种情况时执行代码(如,点击了一个按钮,数据到达,等等);
只有在必要时才运行的代码。
可能会有疑问为什么延迟执行就要用lambda,而不是写个类来调用?
答案是lambda想执行的代码是临时定义的,或者说是多样的、动态的,与直接定义在类里的有本质区别
比如以下一个场景,重复执行一个片段代码N次:
repeat(10, () -> System.out.println(“Hello, World!”));
可以在调用这个方法的时候,传进去任意代码片段的lambda表达式,这样可以很方便的达到目的,也很灵活
上面的repeat定义如下,并不是随便可以接收lambda参数:
1 2 3 4 public static void repeat (int n, Runnable action) { for (int i = 0 ; i < n; i++) action.run(); }
常用函数式接口
函数式接口
参数类型
返回类型
抽象方法名
描述
其他方法
Runnable
无
void
run
作为无参数或返回值的动作执行
Supplier
无
T
get
提供一个T类型的值
Consumer
T
void
accept
处理一个T类型的值
addThen
BiConsumer<T,U>
T,U
void
accept
处理T和U类型的值
addThen
Function<T,R>
T
R
apply
有一个T类型参数的函数
compose,andThen,identity
BiFunction<T,U,R>
T,U
R
apply
有T和U类型参数的函数
addThen
UnaryOperator
T
T
apply
类型T上的一元操作符
compose,andThen,identity
BinaryOperator
T,T
T
apply
类型T上的二元操作符
addThen,maxBy,minBy
Predicate
T
boolean
test
布尔值函数
and,or,negate,isEqual
BiPredicate<T,U>
T,U
boolean
test
有两个参数的布尔值函数
and,or,negate
基本类型的函数式接口
函数式接口
参数类型
返回类型
抽象方法名
BooleanSupplier
none
boolean
getAsBoolean
PSupplier
none
p
getAsP
PConsumer
p
void
accept
ObjPConsumer
T,p
void
accept
PFunction
p
T
apply
PToQFunction
p
q
applyAsQ
ToPFunction
T
p
applyAsP
ToPBiFunction<T,U>
T,U
p
applyAsP
PUnaryOperator
p
p
applyAsP
PBinaryOperator
p,p
p
applyAsP
PPredicate
p
boolean
test
也可以自定义接口,不过推荐使用内置的
内部类基本等同于类,只不过是在类的内部,特点是可以使用所在的类的属性,语法很简单易懂,仔细理解还是比较麻烦的,可以多看看这一章关于内部类的解释
InnerClassTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package cc.singll.learnjava.innerClass;import java.awt.*;import java.awt.event.*;import java.util.*;import javax.swing.*;import javax.swing.Timer;public class InnerClassTest { public static void main (String[] args) { TalkingClock clock = new TalkingClock (1000 , true ); clock.start(); JOptionPane.showMessageDialog(null , "Quit program?" ); System.exit(0 ); } }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 (); Timer t = new Timer (interval, listener); t.start(); } public class TimePrinter implements ActionListener { public void actionPerformed (ActionEvent event) { System.out.println("At the tone, the time is " + new Date ()); if (beep) Toolkit.getDefaultToolkit().beep(); } } }
局部内部类,就是在方法内的内部类,只有方法内可以使用,其他方法不可以使用,也是可以访问所在类的属性
优点就是可以访问方法内部的局部变量
匿名内部类,如果内部类只使用了一次,即只有一次实例化,那么可以使用匿名内部类,语法为:
new SuperType(construction prarmeters)
{
innter class methods and data
}
中文版 :
new 父类或者接口名(父类构造器参数)
{
内部类的方法或者属性
}
可以对比一下正常的实例化与匿名内部类的实例化
1 2 3 4 5 6 7 8 Person queen = new Person ("Mary" );Person count = new Person ("Dracula" ) { public void printname () { System.out.println(this .name); } }
AnonymousInnerClassTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package cc.singll.learnjava.anonymousInnerClass;import java.awt.*;import java.awt.event.*;import java.util.*;import javax.swing.*;import javax.swing.Timer;public class AnonymousInnerClassTest { public static void main (String[] args) { TalkingClock clock = new TalkingClock (); clock.start(1000 , true ); JOptionPane.showMessageDialog(null , "Quit program?" ); System.exit(0 ); } }class TalkingClock { public void start (int interval, boolean beep) { ActionListener listener = new ActionListener () { @Override 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(); } }
如果内部类没有引用任何外部的变量,那么可以定义为静态内部类,然后被外部使用
StaticInnerClassTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 package cc.singll.learnjava.staticInnerClass;public class StaticInnerClassTest { public static void main (String[] args) { double [] d = new double [20 ]; for (int i = 0 ; i < d.length; i++) d[i] = 100 * Math.random(); ArrayAlg.Pair p = ArrayAlg.minmax(d); System.out.println("min = " + p.getFirst()); System.out.println("max = " + p.getSecond()); } }class ArrayAlg { public static class Pair { private double first; private double second; public Pair (double f, double s) { first = f; second = s; } public double getFirst () { return first; } public double getSecond () { return second; } } public static Pair minmax (double [] values) { double min = Double.POSITIVE_INFINITY; double max = Double.NEGATIVE_INFINITY; for (double v : values) { if (min > v) min = v; if (max < v) max = v; } return new Pair (min, max); } }
本章最后一部分,代理
这是一个设计模式,即通过一个代理类来进行调用,不是直接调用,就像平常的代理概念一样,比如如果想看404的网站,那么就可以使用代理。
代理可以实现一些目标:
路由对远程服务器的方法调用
在程序运行期间,将用户接口事件与动作关联起来
为调试,跟踪方法调用
比较难理解,这部分就看一看示例,多看看书,不理解就以后再看
ProxyTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 package cc.singll.learnjava.proxy;import java.lang.reflect.*;import java.util.*;public class ProxyTest { public static void main (String[] args) { Object[] elements = new Object [1000 ]; for (int i = 0 ; i < elements.length; i++) { Integer value = i + 1 ; InvocationHandler handler = new TraceHandler (value); Object proxy = Proxy.newProxyInstance(null , new Class [] {Comparable.class}, handler); elements[i] = proxy; } Integer key = new Random ().nextInt(elements.length) + 1 ; int result = Arrays.binarySearch(elements, key); if (result >= 0 ) System.out.println(elements[result]); } }class TraceHandler implements InvocationHandler { private Object target; public TraceHandler (Object t) { target = t; } public Object invoke (Object proxy, Method m, Object[] args) throws Throwable { System.out.print(target); System.out.print("." + m.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 m.invoke(target, args); } }
0x03 相当懒的我终于回来更新了,也终于继续学Java了
本文前半段的更新时间是 2019-09-03 20:32:14
后半段就如文章首部的时间
真的是相当无语,不过继续学就是了
插播一条,明年的新年目标要立在blog上
打脸见~~