第11章 内部类
内部类就是把类定义在一个包围它的类里面。
内部类有静态内部类和普通内部类,这两者在外部使用时的方式也不一样。但两者的相同点都是可以使用包围类下的所有方法和属性;
public class InnerTest {
private Integer ageOuter;
private static Integer staticAgeOuter;
public void changeAge(Integer newAge){
this.ageOuter = newAge;
}
public static void changeAgeStatic(Integer newAge){
staticAgeOuter = newAge;
}
private Integer getAgeOuter(){
return ageOuter;
}
private Integer getStaticAgeOuterAgeOuter(){
return staticAgeOuter;
}
public class InnerA{
private String name;
public void setName(String newName){
this.name = newName;
}
public Integer getNewAge(Integer age){
changeAge(age);
return InnerTest.this.ageOuter;
}
}
public static class InnerB{
private String name;
public void setName(String newName){
this.name = newName;
}
public Integer getNewAge(Integer age){
InnerTest test = new InnerTest();
test.changeAge(age);
// changeAge(age); 不能直接访问非静态方法
changeAgeStatic(age);
// return staticAgeOuter; 可直接返回静态属性
return test.getStaticAgeOuterAgeOuter();
}
}
public static void main(String[] args) {
InnerTest test = new InnerTest();
InnerA innerA = test.new InnerA();
// innerA = new InnerA(); 非静态内部类不支持直接new
InnerB innerB = new InnerB(); // 静态内部类可直接new
innerB = new InnerTest.InnerB (); // 外部使用需要加上外部类名
System.out.println(innerB.getNewAge(25));
InnerTest.InnerA innerA1 = new InnerTest().new InnerA();
}
}
内部类的使用:
- 向上转型:需要向上转型获取基类时,可用内部类实现接口或继承基类。同时在外部包围类下定义获取基类的方法,返回的对象引用实际上是内部类。
- 在方法和作用域中的内部类:
- 原因1:要实现某个接口,用于创建和返回一个引用;
- 原因2:在解决方案中创建了一个不用公开的类来辅助;
public class Test{
public Destination destination(String s){
final class DestinationImpl implements Destination{
// xxx
void f(){}
}
Destination des = new DestinationImpl();
des.xxxx;
// xxx
return des;
}
public static void main(String[] args){
Test t = new Test();
t.destination("hello").f() ;
}
}
匿名内部类:
创建一个继承XX的匿名内部类,下面例子就是创建了一个继承Contents的匿名内部类。这个被继承的类可以是普通类,也可以是个抽象类。
public class Test{
public Contents contents(){
return new Contents(){
// xxx
}
}
}
嵌套类:
- 被static修饰的内部类可看作是个嵌套类,它只能访问包围类static属性和方法。
- 接口中的任何类都会自动成为public和static的,但是并不实际需要static字段出现修饰。
- 多层嵌套并不影响内部类访问外部类的信息。
为什么需要内部类:每个内部类都可以独立地继承自一个实现,因此外部类是否已经继承了某个实现,对内部类并没有限制。
内部类实际解决了多重实现继承:
- 内部类可以有多个实例,每个实例都有自己的状态信息,独立于外围类对象的信息;
- 一个外围类中可以有多个内部类,他们可以以不同方式实现同一接口,护着继承同一个类。
- 内部类对象的创建时机不与外围类对象的创建捆绑到一起
- 内部类不存在肯能引起混淆的is-a关系,它是独立的实体。
第12章 集合
一组对象聚集在一起,能被一个对象说明,那么这就是集合。
借助泛型,我们可以知道要创建什么类型的对象集合,那么不是这个类型的对象,就无法放入这个集合中,在编译期间就不会通过。
基本概念
- Collection,一个由单独元素组成的序列,并且这些元素要符合一条或多条规则。
- Map,一组键值对,使用键来查找值。
添加一组元素
Arrays.asList方法可以将一个数组转成一个序列集合,该集合的底层数组还是原数组。
String[] students = new String[]{"Tom", "Jerry", "Forest", };
List<String> studentList = Arrays.asList(students);
Collection.addAll接收另一个Collection集合;
String[] students = new String[]{"Tom", "Jerry", "Forest", };
List<String> studentList = Arrays.asList(students);
List<String> emails = new ArrayList<>();
emails.add("1");
emails.add("2");
emails.add("3");
emails.add("33");
emails.addAll(studentList);
Collections.addAll,同样也是接收另一个集合。
List
列表,常用有ArrayList、LinkedList。
ArrayList,擅长随机访问,在集合中间插入或删除元素费时;
LinkedList,在集合中间插入或删除元素很快,不适合查找返回指定位置的元素。
两者的区别是因为底层实现方式不一样,ArrayList是数组对象,LinkedList是链表对象。
Iterator
迭代器,所有实现Collection接口的类,都同样实现了Iterator接口。
public interface Collection<E> extends Iterable<E> {
}
结果就是这些这些集合在循环的时候,本质都是通过迭代器里的方法在进行数据迭代获取展示;
Stack
栈,先进后出,Java1时实现很糟糕,在Java6的时候推出了ArrayDeque完善了栈的功能的实现
Set
不允许集合里有重复值,常用的有HashSet,无序的;TreeSet,有序的。
Map
根据一个键找到一个值,常用HashMap
Java16推出了record关键字,会使得编译器自动生成部分代码:构造器、equals、hashcode、
Queue
队列,先进先出。常用的LinkedBlockingQueue
在并发编程中可以安全转移对象。
PriorityQueue,优先级队列,优先级高的先出。
队列和栈的行为都是通过LinkedList提供的。
for-in 和迭代器
有序集合都能使用for-in语法,和迭代器方法有关。
第13章 函数式编程
函数式编程,通过整合现有代码来产生新的功能,而不是从零开始编写所有内容。
面向对象编程抽象数据,而函数式编程抽象行为。
interface Strategy {
String approach(String msg);
}
static class Unrelated {
static String twice(String msg) {
return msg + " " + msg;
}
}
public class Test{
public static void main(String[] args){
Strategy[] strategies = {new Strategy() {
@Override
public String approach(String msg) {
return msg.toUpperCase() +"!";
}
},
msg1 -> msg1.substring(0, 5),
Unrelated::twice
};
}
}
上述案例代码中, 我们定义了数组,数组里的元素用了三种方式,这三种方式有一个共同的特点就是返回值类型一样。
Java8中的方法引用用两个冒号表示,::,::左边是类名或对象名,右边是方法名,没有参数列表。
Lambda表达式
lambda表达式产生的是函数,而不是类。
基本语法如下:
- 参数;
- 后面根 ->,可以理解为产生;
- 再后面根的都是方法体
- h -> h+ ""; 一个参数
- () -> "", 无参数时要单写括号;
- 多个参数要在括号里逗号分隔。
lambda在递归的处理
下面的代码表达的含义是数字n的阶乘。
在for循环之前的第9行代码,定义函数公式。
interface IntCall{
int call(int arg);
}
public class Test{
IntCall fact;
public static void main(String[] args){
fact = n -> n==0 ? 1 : n * fact.call(n-1);
for(int i=0;i<=10;i++){
fact.call(i);
}
}
}
方法引用:
类名::方法名,是个方法引用,指向类中的方法。
方法签名指的的方法的参数顺序和类型以及返回值,签名一致的方法是可以 用 等号练起来,表示将方法A映射到了方法B上。
interface A{
void aa(String s);
}
class B{
void bb(String s){
//
}
}
public class Test{
static void cc(String s){
//
}
public static void main(String[] args){
B b = new B();
A aa = b::bb;
aa = Test::cc;
}
}
Java中的Runnable类,有个run方法,返回类型是void,那么就可以自定义一个无参且返回类型是void的方法,在代码中来替代Runnable类。
public class List2Str {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(777);
}
}).start();
new Thread(()->{System.out.println(888);}).start();
new Thread(List2Str::HHHH).start();
List2Str list2Str = new List2Str();
new Thread(list2Str::GGG).start();
}
public static void HHHH(){
System.out.println(666);
}
public void GGG(){
System.out.println(666);
}
}
未绑定方法引用(非静态),需要先提供对象,才能使用。
构造器方法引用
class Dog{
Dog(){}
}
interface MakeNoArgs{
Dog make();
}
public static void main(String[] args){
MakeNoArgs no1 = Dog::new;
Dog dog = no1.make();
}
函数式接口
lambda表达式里包含了类型推断,所以对于传统的通过 + 生成字符串时,要确保参数类型包含字符串,否则数字类型相加会出错。
java.util.Function
@FunctionalInterface,要求接口上有这个注解的接口,里面只能有一个方法。
这个方法,有参数/无参数、有返回值/无返回值。例如Runnable接口和Callable接口
高阶函数
高阶函数只是一个能接受函数作为参数或能把函数当返回值的函数。
闭包
指声明在一个函数中的函数,所以lambda表达式就是闭包。
内部类经常用做闭包。
IntSupplier makeFun(int x){
int i=0;
return new IntSupplier(){
public int getAsInt(){
return x+i;
}
}
}
// 对内部类可移除其实现的接口和方法名,简写为
IntSupplier makeFun(int x){
int i=0;
return () -> {
return x+i;
}
}
}
第14章 流
按照操作类型,可将流分为创建、修改、消费流元素 三种,也代表前置、中间、终结三种操作。
流的创建
1.使用Stream.of()方式将一组条目变成一个流。
2.每一个Collection都可以使用stream方法来生成一个流。
随机数流:Random类已经被增强,有了一组可以生成流的方法。
new Random().ints(10,20).boxed(); // 有上下界的随机流
int类型的区间范围:IntStream.range(10,20)
Stream.generate(),创建一个由完全相同的对象组成的流
Stream.iterate(),从第一个参数开始,然后将其穿给第二个参数所有引用的方法。
Stream提供了流生成器builder()
Arrays类中包含了名为stream()的静态方法,可以将数组转换为流。
正则表达式加入了splitAsStream方法
流的中间操作
跟踪与调试:peek()操作就是用来辅助调试的,它允许我们查看流对象而不修改它们;
对流元素进行排序:sorted()方法参数里加入Comparator函数方法进行排序。
移除元素:distinct()移除了流中的重复元素;filter(Predicate)过滤操作只保留符合特定条件的元素,也就是传了过滤参数后,结果返回true的元素。
将函数应用于每个流元素:map(Function):将Function应用于输入流中的每个对象,结果作为输出流继续传递,还有mapToInt、mapToLong、mapToDouble;、
在应用map()期间组合流:flatMap()做两件事:接受生成流的函数,并将其应用于传入元素;然后再将每个流扁平化处理,将其展开为元素。
Optional类型
为了避免流返回结果时有异常,使用Optional类型对返回结果进行了包装。
先检查后处理:ifPresent(Consumer),如果值存在,则用这个值来调用Consumer,否则什么都不做。
orElse(otherObject),如果对象存在,则返回这个对象,否则返回otherObject
orElseGet(Supplier),如果对象存在,则返回这个对象,否则返回使用Supplier函数创建的替代对象。
orElseThrow(Supplier),如果对象存在,则返回这个对象,否则抛出一个使用Supplier函数创建的异常。
创建Optional:empty,返回一个空的Optional
of(value):如果已经知道这个value不是null,可以使用该方法将其抱在一个Optional中
ofNullable(value):如果不知道这个value是不是null,使用这个方法。
Optional对象上操作:filter(Predicate)、map(Function)、 flatMap(Function)
流的终结操作
将流转换为一个数组:toArray(),将流元素转换到适当类型的数组中。
在每个流元素上应用某个终结操作:forEach(Consumer)
收集操作:collect(Collector),使用这个Collector将流元素累加到一个结果集合中。
collect(Surplier, BiConsumer, BiConsumer)和上面类型,但是Supplier会创建一个新的结果集合,第一个BiConsumer是用来将下一个元素包含到结果中的函数,第二个BIConsumer用于将两个值组合起来。
组合所有的元素:reduce(BinaryOperator),使用BinaryOperator来组合所有的流元素。reduce(identity, BinaryOperator)
匹配:allMatch(Predicate)、anyMatch(Predicate)、 noneMatch(Predicate)
选择一个元素: findFirst()、 findAny()
获得流相关信息:count() 、max(Comparator) 、min(Comparator)