ログの出力仕組み

ログの出力仕組み

 

概要:

利用Filter来对log的内容进行组织和出力,Filter的概要内容可以通过下面的链接学习。

 

Java

www.techscore.com

 

过程:

从req里面得到相应的信息,放进MDC,然后出力到log

 

ログの出力方式

名前 概要
AdviceController Controller専用の特殊なメソッド(initBinderやExceptionHandler、ModelAttribute)を複数のControllerで共有することが可能
サーブレットフィルター DispatcherServletの呼び出し前後に共通処理を挟み込むことが可能
HandlerInterceptor DispatcherServletとリクエストハンドラとの間に共通処理を挟み込むことが可能。Controllerに対してだけ共通処理を実行したい場合に利用
AOP アスペクト指向プログラミング

各有什么优缺点需要分析一下。

 

ログのマスキングの実装方式

 

画一张sequence图

 

分析一下XX的Log出力方式

 

MDC

MDC介绍 -- 一种多线程下日志管理实践方式_sunzhenhua0608的专栏-CSDN博客

MDCはマルチスレッド対応のログ管理方式です。

現在のThreadと紐づくHashMap

 

关于Log出力方式,最好画一张类图

在IDEA里面添加一个类图出力的插件。

安装·PlantUML手顺

koboldtodragon.hatenablog.com

其中特别要安装一个一个插件。

 

stackoverflow.com

1Z0-809复习 第3章 Generics and Collections 泛型和集合框架

Generics and Collections

Reviewing OCA Collections

Array and ArrayList

Searching and Sorting

Wrapper Classes and Autoboxing

The Diamond Operator

Working with Generics

Generic Classes

Generic Interfaces

Generic Methods

Interacting with Legacy Code

Bounds

Putting It All Together

Using Lists, Sets, Maps, and Queues

Common Collections Methods

Using the List Interface

Using the Set Interface

Using the Queue Interface

Map

Comparing Collection Types

Comparator vs. Comparable

Comparable

Comparator

Searching and Sorting

Additions in Java 8

Using Method References

Removing Conditionally

Updating All Elements

Looping through a Collection

Using New Java 8 Map APIs

1Z0-809复习 第6章 Exception and Assertion

Exceptions and Assertions

Reviewing Exceptions

Exceptions Terminology

Categories of Exceptions

Exceptions on the OCP

Try Statement

catch里不能有try里面发生不了的checked Exception,
原理也是不能存在到达不了的Exception。

Throw vs. Throws

Creating Custom Exceptions

public class CannotSwimException extends Exception {
    public CannotSwimException() {
        super();
    }
    public CannotSwimException(Exception e) {
        super(e);
    }
    public CannotSwimException(String message) {
        super(message);
    }
}

有3种构造方法。

Using Multi-catch

try {
    throw new IOException();
} catch (FileNotFoundException | IOException e) { } // DOES NOT COMPILE

出FileNotFoundException已经包含在IOException中的错。
这个如果分开写,就跟IOException写在上面一样会出错。但是错误内容不同。

左右没有分别。

Using Try-With-Resources

Try-With-Resources Basics

AutoCloseable

和Closable接口的区别
■■ Closeable restricts the type of exception thrown to IOException.
■■ Closeable requires implementations to be idempotent.

Suppressed Exceptions

public void close() throws IllegalStateException {
    throw new IllegalStateException("Cage door does not close");
}

try (JammedTurkeyCage t = new JammedTurkeyCage()) {
    throw new RuntimeException("turkeys ran off");
} catch (IllegalStateException e) {
    System.out.println("caught: " + e.getMessage());
}
Exception in thread "main" java.lang.RuntimeException: turkeys ran off
atJammedTurkeyCage.main(JammedTurkeyCage.java:20)
Suppressed: java.lang.IllegalStateException: Cage door does not close

最后出力的内容可以看出来,
虽然IllegalStateException发生了,但被Suppress到了RuntimeException里面了。
因此没有catch到IllegalStateException这个错。

自动close的时候是按resource生成的逆序关闭。

public class JammedTurkeyCage implements AutoCloseable {

    @Override
    public void close() throws IllegalStateException {
        throw new IllegalStateException("Cage door does not close");
    }

    public static void main(String[] args) throws IOException {

        try (JammedTurkeyCage t = new JammedTurkeyCage()) {
            throw new IllegalStateException("turkeys ran off");
        } finally {
            throw new RuntimeException("and we couldn't find them");
        }
    }

}
Exception in thread "main" java.lang.RuntimeException: and we couldn't find them
    at com.apibot.JammedTurkeyCage.main(JammedTurkeyCage.java:20)

可以看出来,try里面出的错丢失了,最后finally执行的时候,会丢失掉前面的exception。
而别的用法,因为发生了suppressed exception所以不会被丢失。

Putting It Together

Rethrowing Exceptions

Working with Assertions

The assert Statement

Enabling Assertions

Using Assertions

1Z0-809复习 第9章 NIO.2

NIO.2

  • Use Path interface to operate on file and directory paths
  • Use Files class to check, read, delete, copy, move, manage metadata of a file or directory
  • Use Stream API with NIO.2

NIO.2 中的N是指Non-blocking(非阻塞)
java.io API能做更多的事,具体是指什么?
学习如何读取和修改文件属性。

Introducing NIO.2

java.io 是基于byte stream对文件进行操作。
java 1.4开始增加了 NIO, 基于buffers 和 channels。 什么是channels?

The basic idea is that you load the data from a file channel into a temporary buffer that, unlike byte streams, can be read forward and backward without blocking on the underlying resource.

从文件频道读取数据到一个临时的buffer中。跟byte streams是不同的。
可以无阻赛的前后读取resource。

1.4中的初版NIO没有流行,绝大多数程序员还是在使用java.io API

NIO打算取代 java.io streams,而NIO.2 是取代java.io.File以及相关交互。
NIO.2和NIO要区分对待。NIO.2是为了更加直观,更多特性来操作File。并且对FileClass也有更多的性能改善。

f:id:pinetree-chen:20180927201916p:plain

Introducing Path

Path,用来取代java.io.File,跟File基本相同
区别:Path能够创建,操作symblolic link这个特殊的file。

Creating Instances with Factory and Helper Classes

通过Paths这个工厂类来创建Path的一个instance。Path 本身是一个interface。

为什么要用工厂类而不是直接调用Path实体类的构造函数
原因是创建一个文件或目录是需要基于文件系统、
使用Path Object,所有基于文件系统的特性就是透明的,
相对的java.io.File就不是透明的。
通过不同的Path的Implementation,Java可以文件处理文件系统的不同之处。

NIO.2还有不helper class,例如 java.nio.file.Files,用于操作Path Objects。

Creating Paths

Using the Paths Class

Paths.get("绝对路径/相对路径的文件名");
参数可以是vararg,也可以是URI
注意下面这个用/来表示绝对路径的例子。
Path path3 = Paths.get("/","home","zoodirector");
注意不要搞错了Path和Paths的含义。
下面这个例子是用java.net.URI来创建Path。
Path path3 = Paths.get(new URI("file:///home/zoodirectory"));
注意有3个///,因为URI只能是绝对路径。
另外这个方法一定会抛出URISyntaxException,所以必须try/catch
isAbsolute()是指URI前面带没有带Schema。
Path有toUri()这个方法来将Path转回URI

Accessing the Underlying FileSystem Object

可以通过FileSystems这个工厂类来获取FileSystem这个instance,
然后通过FileSystem来获取Path。
Path path3 = FileSystems.getDefault().getPath("/home/zoodirector");
注意这里是getPath方法。

FileSystem fileSystem = FileSystems.getFileSystem(
    new URI("http://www.selikoff.net"));
Path path = fileSystem.getPath("duck.txt");

通过FileSystem,NIO2增加了访问外部文件系统的能力。
但访问文件系统有什么意义,还是不是很明白。

Path比File的好处:更多的特性,内制支持FileSystem和symbolic links。

Working with Legacy File Instances

File.toPath();
Path.toFile();
一般还是推荐用Path,因为比File有更多好处。

Interacting with Paths and Files

Path的内容并不是实际存在的文件内容,因此即使文件不存在,
很多方法都不会出错。
其中之一toRealPath()会去看文件是否实际存在。
不存在会出NoSuchFileException

Providing Optional Arguments

ATOMIC_MOVE是一个不可分割的原子操作,这个操作让进行观察者看不到没有完成的状态,
或者说只有写了部分的文件。
NOFOLLOW_LINKS,FOLLOW_LINKS COPY_ATTRIBUTES,REPLACE_EXISTING,ATOMIC_MOVE

使用vararg的目的是为了松散耦合这些方法,今后万一增加属性,也不需要修改方法签名。

Using Path Objects

Path的很多方法都是返回Path的instance,因此可以chain起来。例如:
Paths.get("/zoo/../home").getParent().normalize().toAbsolutePath();

Viewing the Path with toString(), getNameCount(), and getName()

getNameCount(),根目录不包含在里面。
如果是一个只有根目录的目录调用该方法,返回的结果是0。

Accessing Path Components with getFileName(), getParent(),and getRoot()

getFileName()返回的是文件名,离root最远的那个元素。
如果是文件,当然返回文件;如果是目录,则是最远的那个目录。
getParent(),返回上层路径,如果本身是根目录,那就返回null。
getRoot();绝对路径就返回根目录,如果是相对路径就返回null。

Checking Path Type with isAbsolute() and toAbsolutePath()

如果已经是绝对路径了,那toAbsolutePath()返回的是一个新的Path Object,跟原来的Path内容一样。

Creating a New Path with subpath()

subpath(int,int),前面是闭口,后面是开口。
返回的是相对路径。
越界了会出java.lang.IllegalArgumentException错。 开始终了的int相同也会出java.lang.IllegalArgumentException。

Using Path Symbols

./ 当前目录; ../ 上层目录

Deriving a Path with relativize()
Path path1 = Paths.get("fish.txt");
Path path2 = Paths.get("birds.txt");
System.out.println(path1.relativize(path2));
System.out.println(path2.relativize(path1));

返回的结果

..\birds.txt
..\fish.txt

特别要注意:不是.\

relativize(path)必须都是绝对路径或者相对路径。
单方面的㐊的话,出java.lang.IllegalArgumentException。

path1.resolve(path2),如果path2是绝对路径。
那么path1就会被放弃,生成一个跟path2内容相同的新的对象。

Cleaning Up a Path with normalize()

E:\data..\user\home 经过normalize(), 变成 E:\user\home

Checking for File Existence with toRealPath()

内部会做normalize(),跟toAbsolutePath()相同,多一个检测文件是否真实存在。

Interacting with Files

Files这个helpclass操作的是Path instance,跟File类完全没有关系,要注意。

Testing a Path with exists()
Testing Uniqueness with isSameFile()

Files.isSameFile(Path,Path) 首先比较两个Path是不是相同,如果是相同的,就不去看文件是否真实存在。 如果两个Path的结果是不同的,会去locate两个实际文件,得到其绝对路径,看其是否相同。
文件完全相同,但在不同路径的话,就是不同的。

try {
    System.out.println(Files.isSameFile(Paths.get("/user/home/cobra"),
    Paths.get("/user/home/snake")));//相同
    System.out.println(Files.isSameFile(Paths.get("/user/tree/../monkey"),
    Paths.get("/user/monkey")));//相同
    System.out.println(Files.isSameFile(Paths.get("/leaves/./giraffe.exe"),Paths.get("/leaves/giraffe.exe")));//相同
    System.out.println(Files.isSameFile(Paths.get("/flamingo/tail.data"),
    Paths.get("/cardinal/tail.data")));//不同
} catch (IOException e) {
// Handle file I/O exception...
}
Making Directories with createDirectory() and createDirectories()

相对于File的mkdir()和mkdirs()

Duplicating File Contents with copy()

Files.copy(Path,Path),
目录拷贝时不拷贝子目录,也不拷贝文件。那跟直接建立一个目录有什么区别?
拷贝行为,可以通过NOFOLLOW_LINKS,REPLACE_EXISTING, and COPY_ATTRIBUTES, 这些option来改变。

Changing a File Location with move()

可以用来移动或者改名。

移动一个空目录到另一个drive是可以的。
移动一个非空目录到另一个drive will throw an NIO.2 DirectoryNotEmptyException

Removing a File with delete() and deleteIfExists()

Files.delete(Path)只能删除空的目录或者文件,
如果目录非空,则出DirectoryNotEmptyException
如果target是一个symbol link,那么这个symbol link被删掉,对象目录不会被删掉。

deleteIfExists(Path),如果不存在不会出错。返回false

Reading and Writing File Data with newBufferedReader() and newBufferedWriter()

Files.newBufferedReader(Path,Charset),需要事先定义字符集或者用缺省的字符集(Charset.defaultCharset())。
推荐用Buffer类,提高性能。

后面用lambda表达式,更快更简单。

Reading Files with readAllLines()

后面新方法,处理更大的文件。

Understanding File Attributes

file attributes = metadata

Discovering Basic File Attributes

Reading Common Attributes with isDirectory(), isRegularFile(), and isSymbolicLink()

不需要调用exists(),不存在就是false。

Checking File Visibility with isHidden()
Testing File Accessibility with isReadable() and isExecutable()

同样,如果不存在就是false,而不是Exception

Reading File Length with size()

目录的大小没法用这个方法,在后面会讲到。

Managing File Modifications with getLastModifiedTime() and

setLastModifiedTime() 返回的是FileTime这个对象。
Files.setLastModifiedTime(Path,FileTime) 设值的时候也需要用FileTime。

Managing Ownership with getOwner() and setOwner()

返回的是UserPrincipal对象。
Files.setOwner(Path,UserPrincipal) 设值的时候也需要用UserPrincipal。

try {
    // Read owner of file
    Path path = Paths.get("/chicken/feathers.txt");
    System.out.println(Files.getOwner(path).getName());
    // Change owner of file
    UserPrincipal owner = path.getFileSystem()
        .getUserPrincipalLookupService().lookupPrincipalByName("jane");
    Files.setOwner(path, owner);
    // Output the updated owner information
    System.out.println(Files.getOwner(path).getName());
} catch (IOException e) {
    // Handle file I/O exception...
}

Improving Access with Views

如果要读入很多FileAttribute,一次读取一个肯定会耗费很多IO资源。
因此有一个view,一次读入多个属性,性能提高会比较高。
即使真的只读入一个属性,也可以用View的方法。性能也不会差很多。
但是用一个的方法更好一点。

Understanding Views

Files.readAttributes()返回一个只读的view Files.getFileAttributeView(),返回一个可以更新的view

  • BasicFileAttributes: 基本的文件属性View
  • DosFileAttributes : DOS/Windows的文件属性View
  • PosixFileAttributes: Unix/Linux的文件属性View
Reading Attributes
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;

public class BasicFileAttributesSample {

    public static void main(String[] args) throws IOException {
        Path path = Paths.get("C:/A01.Chen/09_陳 剛/fj3731iv.p12");
        BasicFileAttributes data = Files.readAttributes(path,
            BasicFileAttributes.class);
        System.out.println("Is path a directory? " + data.isDirectory());
        System.out.println("Is path a regular file? " + data.isRegularFile());
        System.out.println("Is path a symbolic link? " + data.isSymbolicLink());
        System.out.println("Path not a file, directory, nor symbolic link? " +
            data.isOther());
        System.out.println("Size (in bytes): " + data.size());
        System.out.println("Creation date/time: " + data.creationTime());
        System.out.println("Last modified date/time: " + data.lastModifiedTime());
        System.out.println("Last accessed date/time: " + data.lastAccessTime());
        System.out.println("Unique file identifier (if available): " +
            data.fileKey());
    }
}
Modifying Attributes
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.*;
public class BasicFileAttributeViewSample {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("/turtles/sea.txt");
        BasicFileAttributeView view =
            Files.getFileAttributeView(path,BasicFileAttributeView.class);
        BasicFileAttributes data = view.readAttributes();
        FileTime lastModifiedTime = FileTime.fromMillis(
            data.lastModifiedTime().toMillis()+10_000);
        view.setTimes(lastModifiedTime,null,null);
    }
}
void setTimes(FileTime lastModifiedTime,
              FileTime lastAccessTime,
              FileTime createTime)

Presenting the New Stream Methods

Conceptualizing Directory Walking

遍历目录树的概念。

Selecting a Search Strategy

Streams API uses depth-first searching with a default maximum depth of Integer.MAX_VALUE.

Walking a Directory

Files.walk(path)返回一个Stream 对象。lazy manner

Path path = Paths.get("C:/A01.Chen/09_陳 剛");
try {
    Files.walk(path)
        .filter(p -> p.toString().endsWith(".xlsx"))
        .forEach(System.out::println);
} catch (IOException e) {
    // Handle file I/O exception...
}

这个非常好用,可以取回某个目录下的所有的.xlsx文件。

Searching a Directory

Path path = Paths.get("C:/A01.Chen/09_陳 剛");
long dateFilter = 1420070400000l;
try {
    Files.find(path, 10,
        (p, a) -> p.toString().endsWith(".xlsx")
            && a.lastModifiedTime().toMillis() > dateFilter)
        .forEach(System.out::println);
} catch (Exception e) {
    // Handle file I/O exception...
}

感觉这个还是有点怪的,似乎一定要加上第二个条件

Listing Directory Contents

Path path = Paths.get("C:/A01.Chen/09_陳 剛");
Files.list(path)
    .filter(p -> !Files.isDirectory(p))
    .map(p -> p.toAbsolutePath())
    .forEach(System.out::println);

跟walk的区别是这个只返回一层里面所有的内容。

Printing File Contents

Files.readAllLines(Paths.get("birds.txt")).forEach(System.out::println);
Files.lines(Paths.get("birds.txt")).forEach(System.out::println);

Comparing Legacy File and NIO.2 Methods

1Z0-809复习 第1章 Advanced Class Design

Advanced Class Design

Reviewing OCA Concepts

Access Modifiers

Overloading and Overriding

Abstract Classes

Static and Final

Imports

Using instanceof

A instanceof B,如果A是B,或B的子类,则是true,如果不是则是false。
如果A是null,则永远是false,如果B是Object,除非A是null,否则永远是true。

Understanding Virtual Method Invocation

virual method :首先是非static得方法。 例如子类override了父类的一个方法,那么在实际调用该方法的时候。 java会寻找使用合适的方法,而不是根据编译的类里面的那个方法。

这个在以前也OCA里面已经有这个概念,在OCP中只是明确了叫virtual method的方法。

如果没有A跟比没有任何继承关系,那么会出CompileError。

如果B是interface,那么会在运行期再进行检查。

public interface Mother {}
class Hippo extends HeavyAnimal { }

HeavyAnimal hippo = new Hippo();
boolean b6 = hippo instanceof Mother;

上面这段source不会出错。 如果后续有下面一个定义。

class MotherHippo extends Hippo implements Mother { }
abstract class Animal {
    String name = "???";
    public void printName() {
        System.out.println(name);
    }
}
class Lion extends Animal {
    String name = "Leo";
}
public class PlayWithAnimal {
    public static void main(String... args) {
        Animal animal = new Lion();
        animal.printName();
    }
}

成员变量跟成员方法不一样,不存在override,
其只存在hidden,因此这个输出结果是???而不是Leo。 这也是多态的一个特点。

abstract class Animal {
    public void careFor() {
        play();
    }
    public void play() {
        System.out.println("pet animal");
    }
}
class Lion extends Animal {
    @Override
    public void play() {
        System.out.println("toss in meat");
    }
}
public class PlayWithAnimal {
    public static void main(String... args) {
        Animal animal = new Lion();
        animal.careFor();
    }
}

生成了一个lion的对象,参照类型设置为父类Animal。
因为careFor只在animal里面有,所以调用的是Animal的careFor。
Animal的careFor调用了play,但这个时候java会看play是否有重载。
发现lion中有play的override方法,因此调用Lion里面的play。
即使参照对象是父类Animal,调用的还是子类Lion的play,这个就是多态。

Annotating Overridden Methods

override方法可以用注释@override显示声明。
annotation是一种元数据,可以在编译期或实行期使用。

当出现@override,说明有以下3种情况之一

  • 实现了接口的某个方法。
  • override了父类的某个方法。
  • override了Object的方法,例如toString,equals,hashCode。

Coding equals, hashCode, and toString

java中所有类都是直接或间接继承了Object,因此也继承了Object类的所有方法。

toString

java中的print方法,会自动调用打印对象的toString()方法。

对象如果实装了toString()方法,使得打印的对象内容更清楚明了。
譬如ArrayList,[1,2,3]
如果对象使用Object的toString()方法,例如array对象
[Ljava.lang.String;@65cc892e]

一般的做法是打印出对象的所有成员变量,但一个一个写的话非常麻烦。
Apache社区提供了上述opensource,利用其提供的方法,
可以非常方便的打印出对象中所有成员变量。

//定义build.gradle
compile("org.apache.commons:commons-lang3:3.8")
//方法重载
public String toString() {
    return ToStringBuilder.reflectionToString(this);
}

这其中利用到了反射,在实行期取得类的信息。
所有source:

import org.apache.commons.lang3.builder.ToStringBuilder;

public class Hippo {
    private String name;
    private double weight;
    public Hippo(String name, double weight) {
        this.name = name;
        this.weight = weight;
    }
    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }
    public static void main(String[] args) {
        Hippo h1 = new Hippo("Harry", 3100);
        System.out.println(h1); // Harry
    }
}

执行结果:com.apibot.Hippo@78308db1[name=Harry,weight=3100.0]

//简略版
public String toString() {
    return ToStringBuilder.reflectionToString(this,ToStringStyle.SHORT_PREFIX_STYLE);
}

执行结果:Hippo[name=Harry,weight=3100.0]

equals

如果类重载了equals方法,那么利用重载的方法进行比较。

String s1 = new String("lion");
String s2 = new String("lion");
System.out.println(s1.equals(s2)); // true
StringBuilder sb1 = new StringBuilder("lion");
StringBuilder sb2 = new StringBuilder("lion");
System.out.println(sb1.equals(sb2)); // false

这个应该是在StringBuilder类中equals没有重载,
因此用的是Object的equals方法来比较。
而String在重载的equals方法中,比较的是每个字符串的每个字符,所以是相等的。
- reflexive :反身性的,x.equals(x) 肯定是true
- symmetric: :对称的,如果x.equals(y)是true,那么y.equals(x)肯定也是true
- transitive: :传递性的,如果x.equals(y)是true,y.equals(z),那么 x.equals(z)也应该是相等的。
- consistent: : - x.equals(null) 永远都是false。而不是出NullPointerException

public boolean equals(Lion obj) {
    if (obj == null) return false;
    return this.idNumber == obj.idNumber;
}

传进来的参数如果不是Object类型,那么这个就不是Override,而是Overload。
虽然没有任何问题,但还是要注意

public boolean equals(Object obj) {
    return EqualsBuilder.reflectionEquals(this, obj);
}

重载之后比较的就是对象的所有成员变量。

public boolean equals(Object obj) {
    if ( !(obj instanceof LionEqualsBuilder)) return false;
    Lion other = (Lion) obj;
    return new EqualsBuilder().appendSuper(super.equals(obj))
                              .append(idNumber, other.idNumber)
                              .append(name, other.name)
                              .isEquals();
}

重载之后,比较的是对象中的idNumber和name。 虽然不是很简洁,还是得手动处理null和instanceof,但毕竟比所有都全手写要方便。

hashCode

当把object作为key存到map里去的话,会需要用到hashcode

public class Card {
    private String rank;
    private String suit;
    public Card(String r, String s) {
        if (r == null || s == null) {
            throw new IllegalArgumentException();
        }
        rank = r;
        suit = s;
    }
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Card)) {
            return false;
        }
        Card c = (Card) obj;
        return rank.equals(c.rank) && suit.equals(c.suit);
    }
    @Override
    public int hashCode() {
        return rank.hashCode();
    }
}

如果是primitive数字,可以直接用,或者取更小的int作为hashcode。
另外,一般在hashcode中不包含boolean和char类型的变量。

  • 同样的program,hashcode()的结果不会变。
    意味着不可以包含会变的变量。
    前面的Hippo类例子,只包括名字,因为体重会变。

  • 如果equals()的结果是true,那么hashCode()会返回相同的结果。
    这个意味着hashCode使用equals比较的成员变量的子集。
    例如上面Card的例子,有两个变量用于equals,但只有其中的一个用于hashCode。

  • 另一个角度讲,如果equals()的结果是false,hashCode()并不需要返回相同的结果。

//没问题,hashCode和equals用相同的变量
public int hashCode() { return idNumber; }
//没问题,但没有意义,永远返回相同的6
public int hashCode() { return 6; }
//方法名里的c不是C,因此是一个全新的方法。如果是C也有问题,override的时候返回值类型只能是子类。
public long hashcode() { return idNumber; }
//这个有问题,因为用到的参数比equals多,idNumber以外用了age
public int hashCode() { return idNumber * 7 + age; }

Working with Enums

public enum Season {
    WINTER, SPRING, SUMMER, FALL //没有分号
}

Enum值最后是没有分号的。
Enum和常数的区别在于,其是Type Safe,也就是在编译之前就知道这些成员变量的type。
enum包含的是static变量,以及helper method。

Season s = Season.SUMMER;

关于这个用法稍微有点违和感,貌似把类的成员变量赋予了类。

System.out.println(s == Season.SUMMER); // true

看上去像是两个字符串用等号来比较,有违和感。
可以想象成这是两个一开始就定义好的static final的变量。
这样用等号就没有违和感了。
enum的toString()起作用时,取得是enum的name

for(Season season: Season.values()) {
    System.out.println(season.name() + " " + season.ordinal());
}
System.out.println(Season.SPRING.name() + " " + Season.SPRING.ordinal());

这个看上去也有违和感。是因为Season的静态方法values()会取得其中的所有元素。
循环这个array,用name()方法取得每个元素的名字。用ordinal()方法,取得每个元素的值。

可以看出来每一个元素都有一个name一个顺序int值

Season s1 = Season.valueOf("SUMMER"); // SUMMER
Season s2 = Season.valueOf("summer"); // java.lang.IllegalArgumentException

可以用完全一模一样的字符串创建一个enum

Season.SPRING.values(); // Season的所有元素,少用
Season.values();        // Season的所有元素,相同

Using Enums in Switch Statements

Season summer = Season.SUMMER;
switch (summer) {
//这个用法是错的,因为:
//Season.WINTER是一个enum,并不是enum的int
//WINTER可以称为一个enum的value,是case可以接受的类型。
//Season可以称作enum的type。
case Season.WINTER:
    System.out.println("Get out the sled!");
    break;
case SUMMER:
    System.out.println("Time for the pool!");
    break;
default:
    System.out.println("Is it summer yet?");
}

最终针对Season.SUMMER了解几个概念:

Adding Constructors, Fields, and Methods

public enum Season {
    WINTER("Low"), SPRING("Medium"), SUMMER("High"), FALL("Medium");
    //成员变量
    private String expectedVisitors;
    //构造函数
    private Season(String expectedVisitors) {
        this.expectedVisitors = expectedVisitors;
    }
    //成员方法
    public void printExpectedVisitors() {
        System.out.println(expectedVisitors);
    }
}
  • 如果enum类里面只有value的一行,那么最后一个分号是可以省略的。
    像上面的情况,最后一个分号就不可以省略。
  • 构造方法的修饰词必须是private,因为只可以在enum之内被调用。public是无法通过编译的。
  • 构造函数可以看出来,是将方法的形参赋给成员变量。 再看第一行的定义就明白了,Low,Medium,High就是形参的具体值。
Season.SUMMER.printExpectedVisitors();

执行结果是High,因为SUMMER("High")调用构造函数把High赋给了SUMMER 另一enum的用法:

public enum Season {
    WINTER {
        @Override
        public void printHours() {
            System.out.println("9am-3pm");
        }
    },
    SPRING {
        @Override
        public void printHours() {
            System.out.println("9am-5pm");
        }
    },
    SUMMER {
        @Override
        public void printHours() {
            System.out.println("9am-7pm");
        }
    },
    FALL {
        @Override
        public void printHours() {
            System.out.println("9am-5pm");
        }
    };
    public abstract void printHours();
}
Season summer = Season.SUMMER;
summer.printHours();

Creating Nested Classes

嵌套类是定义在另一个类里面的类。
如果是非static,则叫做内部类inner class。

  • 成员内部类和成员变量同等级定义,非static。
    通常是指不显示定义类型的内部类。
  • 本地内部类,定义在方法体内。
  • 匿名内部类是一个没有名字的本地内部类的。
  • 静态嵌套类,是跟static变量同级别的静态类。

用嵌套类的好处之一:就是可以封装helper class到容器类中

Member Inner Classes

member level(和成员方法,成员变量和构造方法一级)定义的类。
- 可以定义,public,private,protected,default access
- 可以继承任何其他类或接口 - 可以是抽象类和final - 不可以定义静态成员和方法。 - 可以访问外部类的成员变量,包括private成员变量。 最后两条非常重要。

public class Outer {
    private String greeting = "Hi";
    protected class Inner {
        public int repeat = 3;
        public void go() {
            for (int i = 0; i < repeat; i++) {
                System.out.println(greeting);
            }
        }
    }
    public void callInner() {
        Inner inner = new Inner();
        inner.go();
    }
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.callInner();
        Outer outer2 = new Outer();
        // 一开始以为应该是 new outer2.Inner();
        // 但我错了,要记住这个格式
        Inner inner2 = outer2.new Inner();
    }
}

编译后的.class文件。一个是Outer.class,一个是Outer$Inner.class InnerClass可以和OuterClass有相同名字的成员变量。

public class A {
    private int x = 10;
    class B {
        private int x = 20;
        class C {
            private int x = 30;
            public void allTheX() {
                System.out.println(x);        // 30
                System.out.println(this.x);   // 30
                System.out.println(B.this.x); // 20
                System.out.println(A.this.x); // 10
            }
        }
    }
    public static void main(String[] args) {
        A a = new A();
        A.B b = a.new B();
        A.B.C c = b.new C();
        c.allTheX();
    }
}

可以从A.B省略成B,因为类B和main方法在同一个member level
但不可以把A.B.C省略成C,因为C在下一层。
另外可以增加一句import A.B.C,这样就可以直接用C了。

private interface
public class CaseOfThePrivateInterface {
    private interface Secret {
        public void shh();
    }
    class DontTell implements Secret {
        @Override
        public void shh() {
        }
    }
}

关于interface的规则没有变: - 成员方法缺省都是public,实装的方法必须是public的。
- interface本身不必须是public,跟内部类相同,inner interface可以是private的。
同时也意味着这个interface只能被outerclass使用。

Local Inner Classes

local inner class 定义直到方法被调用的时候才产生。
然后到方法返回时就失效了。
所有的动作跟成员变量相同。
- 不可以有修饰符。 - 不能声明为static,也不能有static的成员变量和方法。 - 可以访问包围类的所有成员和方法。 - 他们没有权限访问本地变量,除非这些变量是final或者等效final。

public class Outer {
    private int length = 5;
    public void calculate() {
        //这里是final所有没有问题
        //如果是 int width = 20; width = 30; 就会出错。
        final int width = 20;
        class Inner {

            public void multiply() {
                System.out.println(length * width);
            }
        }
        Inner inner = new Inner();
        inner.multiply();
    }
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.calculate();
    }
}

一个是instance变量,一个是final变量,因此在innerclass内可以访问。

effecitvely final

不需要显示定义,实际上只发生一次赋值的变量都是 effecitvely final

Anonymous Inner Classes

就是没有名字的Local Inner Class。

public class AnonInner {
    abstract class SaleTodayOnly {
        abstract int dollarsOff();
    }
    public int admission(int basePrice) {
        SaleTodayOnly sale = new SaleTodayOnly() {
            @Override
            int dollarsOff() {
                return 3;
            }
        };
        return basePrice - sale.dollarsOff();
    }
}

看上去像是new了一个抽象类,抽象类是不能被new的。
但只要有方法体{},就可以(多态?)
不是抽象类,接口也可以。 interface 的方法缺省是public的。
但抽象类的方法,是default的。
无法继承一个class的同时实现一个接口。
除非要继承的是java.lang.Object <-这句话什么意思。

如果真有这个需求,那么最好是定义成local inner class
然后然后就可以继承类和实现接口了。
如果真的需要这么复杂,内部类本身就可能并不是一个合适的选择。

public class AnonInner {
    interface SaleTodayOnly {
        int dollarsOff();
    }
    public int pay() {
        return admission(5, new SaleTodayOnly() {
            @Override
            public int dollarsOff() {
                return 3;
            }
        });
    }
    public int admission(int basePrice, SaleTodayOnly sale) {
        return basePrice - sale.dollarsOff();
    }
}

Static Nested Classes

静态类的用法跟静态方法一样,例如不能直接访问外部类的成员变量。

public class Enclosing {
    static class Nested {
        //静态类里面也可以定义静态方法,但是需要有静态类对象才能访问。
        private int price = 6;
    }
    public static void main(String[] args) {
        //初始化的时候不需要Enclosing的对象就可以初始化,这跟内部成员类有区别。
        Nested nested = new Nested();
        //生成了静态类的对象,然后访问。
        System.out.println(nested.price);
    }
}

import静态类时的两个方法。
普通方法: import Enclosing.Nested
静态导入: import static Enclosing.Nested

不是静态内部类,只能用普通方法import。
嵌套类import的时候,outerclass看上更像是一个namespace。

Summary

Exam Essentials

Review Questions

《Java8函数式编程》学习笔记 - 第9章

第9章 使用Lambda表达式编写并发程序

前面的章节介绍如何并行化处理出具,本章讨论如何使用Lambda表达式编写并发应用,
高效传递信息和非阻塞式I/O

为什么要使用非阻塞式I/O

使用传统线程模型,每次向用户写数据时,都要调用一个方法向用户传输数据,
这个方法会阻塞当前流程。
这种方式叫阻塞式I/O,是一种通用且易于理解的方式。因为和程序用户的交互通常
符合这样一种顺序执行的方式。
缺点是将系统扩展至支持大量用户时,需要和服务器建立大量TCP连接,因此扩展性不是很好。

非阻塞式I/O,有时也叫异步I/O,可以处理大量并发网络连接,而且一个线程可以为多个连接服务。
和阻塞式I/O不同,对聊天程序客户端的读写调用立即返回。
真正的读写操作则在另一个独立的线程执行,这样就可以同时执行其他任务了。

Java标准类库的NIO提供了非阻塞式I/O的接口,NIO的最初版本用到了Selector的概念,
让一个线程管理多个通信管道,比如向客户端写数据的网络套接字。
然后这种方式没有在Java程序员中流行起来,它编写出来的代码难于理解和调试,
引入Lambda表达式之后,设计和实现没有这些缺点的API就顺手多了。

9.2 回调

public class ChatVerticle extends Verticle {
    public void start() {
        vertx.createNetServer()
             .connectHandler(socket -> {
                container.logger().info("socket connected");
        socket.dataHandler(new User(socket, this));
    }).listen(10_000);
    container.logger().info("ChatVerticle started");
    }
}

well encapasulated是指所有成员变量都是private,都只能通过public成员方法访问。 char可以作为int,传给int形参的方法。 primitive中,char型和int型可以互转。 既然有default constructor了,说明本身就没有其他构造方法,那自然是没有overload一说的。

数组的初始化可以是空的大括号,表示不初始化。 多维数组初始化时,如果前面没有初始化,则后面初始化也没有。 int x = new int 第一个必须被初始化,前面一个是空,后面一个初始化了这种情况不被允许。 int[1][][1]是不行的。

父类成员方法即使不throws任何Exception,
子类override的成员方法也可以随意throws RuntimeException

throw Exception()这个格式表示的是某个名为Exception的方法,其返回值是一个Exception类。
然后把返回的这个Exception throw出去。
跟throw new Exception()是一个意思。

int[] x = new int[5];
x = new int[]{1,2,3,4};
int y = x[1]+ x[0]-- /x[0] * x[4];
System.out.println(y);

当中是自减1之后再去除以,因此抛ArithmeticException
如果第一位1⇒2,那么就是ArrayIndexOutOfBoundsException

interface里的print方法缺省是public abstract,
因此在实现该方法时候必须是public,这是根据override原则子类方法必须比父类方法范围更宽。

数组不可以指定index并且同时初始化 int[] x = new int[2]{1,2}就是错的。

be
c
d
b
cd
a
d
acd
ac
h
j
14 c 1+3+4
15 d
16 d
17 a
18 d
19 aeg
20 ab
21 bd

嵌套类定义在另一个类的内部。

inner class 内部类

static nested classes 静态嵌套类

  • member nested class(成员嵌套类):
    成员嵌套类作为enclosing class的成员定义的,成员嵌套类有enclosing class属性

  • local nested class (局部嵌套类):
    局部嵌套类定义在enclosing class的方法里面,
    局部嵌套类有enclosing class属性和enclosing method属性

  • anonymous nested class(匿名嵌套类):
    匿名嵌套类没有显示的定义一个类,直接通过new的方法创建类的实例。
    一般回调模式情况下使用的比较多

public class EnclosingClass {
    public static final class NestedMemberClass {
    }
    public void nestedLocalClass() {
        final class NestedLocalClass {
        }
    }
    public void nestedAnonymousClass() {
        new Runnable() {
            @Override
            public void run() {
            }
        };
    }
}

1Z0-809复习 第7章 并发

Concurrency

Introducing Threads

Distinguishing Thread Types

Introducing Threads

Understanding Thread Concurrency

Introducing Runnable

Creating a Thread

Polling with Sleep

daemon threads:不阻止JVM退出的线程

Creating Threads with the ExecutorService

ExecutorService是用来创建和管理线程。
首先获得一个ExecutorService接口的实例。
然后把task交给这个service去执行。
ExecutorService框架包含很多有用的特性,例如线程池和实行计划。

Introducing the Single-Thread Executor

public class ZooInfo {
    public static void main(String[] args) {
        ExecutorService service = null;
        try {
            service = Executors.newSingleThreadExecutor();
            System.out.println("begin");
            service.execute(() -> System.out.println("Printing zoo inventory"));
            service.execute(() -> {for(int i=0; i<3; i++)
                System.out.println("Printing record: "+i);}
                    );
            service.execute(() -> System.out.println("Printing zoo inventory"));
            System.out.println("end");
        } finally {
            if(service != null) service.shutdown();
        }
    }
}
begin
Printing zoo inventory
Printing record: 0
Printing record: 1
end
Printing record: 2
Printing zoo inventory
  • 通过工厂类Executors获得ExecutorService接口的实例类newSingleThreadExecutor
  • service是一个单线程Executor,因此传给它的3个task是按顺序执行的。
  • begin,end是main这个Thread的结果,跟service不是同一个Thread, 因此end在当中出现了。

虽然单线程Executor是按照提交顺序执行任务,但编程时最好避免依赖这个原则。 因为多线程Executor并不按照这个原则

Shutting Down a Thread Executor

Thread Executor会在执行第一个task的时候产生一个非daemon线程,
因此如果不调用shutdown(),应用会永远没有结束。

执行了shutdown()后,Executor不再接受任何新的线程。 isShutdown()也是true了。isTerminated()还是false。
当Executor执行完了所有线程,则isTerminated()也是true。

shutdownNow(),立即结束所有正在执行和未执行的thread。
返回一个Runnable的list,内容是所有提交给了Executor但还没有开始的线程。

Q:那如果不是Runnable而是Callable的线程怎么返回?

ExecutorService没有实现AutoCloseable接口,因此没法用try-with-resources
只能在finally块中关掉。

问题来了,如果这个Exectuor是静态的。
那么必须自定义一个静态方法,随时随地可以调用来结束应用。

Submitting Tasks

除了execute(Runnable command)之外,还可以用submit。
submit返回一个Future对象,用来判断任务是否结束。
当线程结束后,这个对象可以用来返回一个泛型结果对象。

还可以用invokeAll/invokeAny
参数Collection里的所有线程全部结束,还是其中之一结束。 这两个方法内的线程是同期的。

Waiting for Results

利用Future对象的方法, boolean isDone() V get(long timeout,TimeUnit unit) 可以判断线程是否执行完了以及取得Callable的结果。

Introducing Callable
service.submit(() -> {Thread.sleep(1000); return null;});
service.submit(() -> {Thread.sleep(1000);});

Thread.sleep(1000);会抛出一个InterruptedException, 因此要么带有try/catch,要么本身能够支持checked Exception。

上面行编译通过是因为有显式的返回值,编译器认为其是Callable表达式。 因为Call表达式支持checked exception,所以通过。

下面行通不过编译是因为没有return,编译器认为是Runnable表达式。 因此编译不过。

Waiting for All Tasks to Finish

Scheduling Tasks

ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();

用ScheduledExecutorService来实现。

schedule(Callable<V> callable,long delay, TimeUnit unit)
schedule(Runnable command,long delay, TimeUnit unit)

Future<?> result2 = service.schedule(() -> "Monkey";, 8, TimeUnit.MINUTES);

返回的是ScheduledFuture
在8分钟后执行任务。

service.scheduleAtFixedRate(command,5,1,TimeUnit.MINUTE);

在一个初始5分钟的delay后,每隔1分钟执行一次command。
如果线程执行时间超过间隔时间,ScheduledExecutorService还是会按照间隔时间发起一个新的线程。
线程会连续执行。

scheduleAtFixedDelay() 和scheduleAtFixedRate() 都不接受Callable作为参数。
只要Executor没有完了,会不断产生Future对象。

Increasing Concurrency with Pools

  1. newCachedThreadPool()
  2. newFixedThreadPool(int nThreads)
  3. newScheduledThreadPool(int nThreads)

上面3个方法创建带线程池的Executor。
newCachedThreadPool() 创建的线程池没有大小。
两种情况下创建新的线程,
1是接收到创建线程的要求,2是所有线程都忙。
常用在需要习性大量短生命周期的非同期任务。

长生命周期不适用是因为有可能因此产生大量线程。

newScheduledThreadPool()的scheduleAtFixedRate()
跟ScheduledExecutorService的scheduleAtFixedRate()有着细微的差别
当执行时间超过间隔时间时,间隔时间不变,多个线程同时执行。

Choosing a Pool Size

一般如果是CPU的任务的话,线程池不要太大,
但如果是访问数据库,文件之类线程,因为CPU大多是在等待,
线程池可以大一点。
可用CPU数量:Runtime.getRuntime().availableProcessors()

Synchronizing Data Access

public class SheepManager {
    private int sheepCount = 0;
    private void incrementAndReport() {
        System.out.print((++sheepCount)+" ");
    }
    public static void main(String[] args) {
        for (int x=0;x<10;x++){
            ExecutorService service = null;
            try {
                service = Executors.newFixedThreadPool(20);
                SheepManager manager = new SheepManager();
                for(int i=0; i<10; i++)
                    synchronized(manager) {
                        service.submit(() -> manager.incrementAndReport());
                    }
            } finally {
                if(service != null) service.shutdown();

            }
        }

    }
}

对于sheepCount这个变量,由于多线程访问,且非线程安全
打印结果有可能是下述结果。1 2 3 6 5 4 4 7 8 9
线程同时执行造成的不确定结果称作竞争危害(race condition)
还有一个现象就是,6在5之前被打印了。

Protecting Data with Atomic Classes

Atomic首先是operation的属性,

Improving Access with Synchronized Blocks

for(int i=0; i<10; i++) {
    synchronized(manager) {
        service.submit(() -> manager.incrementAndReport());
    }
}
private void incrementAndReport() {
    synchronized(this) {
        System.out.print((++sheepCount)+" ");
    }
}

上面个解决不了是因为manager被同期话了,也就是说线程创建是一个一个了。
但实行没有。
下面一个就是针对实行时按一个线程。

Synchronizing Methods

Understanding the Cost of Synchronization

Using Concurrent Collections

Introducing Concurrent Collections

Understanding Memory Consistency Errors

Working with Concurrent Classes

Obtaining Synchronized Collections

Working with Parallel Streams

Creating Parallel Streams

Processing Tasks in Parallel

Processing Parallel Reductions

Managing Concurrent Processes

Creating a CyclicBarrier

Applying the Fork/Join Framework

Identifying Threading Problems

Understanding Liveness

Managing Race Conditions

1.D,F
2.A,C,D,F
3.A
4.C 我觉得第一个是递增,第二个结果会变
5.D
6.B,D
7.不会,x2应该出错?
8.G,findAny是返回sort操作最快的元素。
9.E 有返回值应该是RecursiveTask,因为Pool的大小是1,所以相等于singlethread
10 不会
11 A,F
12 A
13.A  the stream created by flatMap() is a new stream that is not parallel by default, even though its elements are parallel streams. Therefore, the performance will be single-threaded and G is correct.
14.D
15.CEG
16.A A是错的,printed本身也是无序的
17.B 因为是单线程的
18.F
19.ADF 一个是instance,一个是this,应该是同一个东西。E不对是因为增加没有用多线程管理
The synchronized object on line k1 is TicketManager.class, while the synchronized object
on line k4 is the instance of TicketManager. 
所以F是错的
20 AD
21 ACDE 
22 F