不安分的黑娃
踏踏实实,坚持学习,慢慢就懂了~

Java 8 学习记录

官方文档

https://docs.oracle.com/javase/8/
https://docs.oracle.com/javase/8/docs/index.html
https://docs.oracle.com/javase/tutorial/index.html

API 文档地址

https://docs.oracle.com/javase/8/docs/api/

新特性

1. Java 语言

  1. Lambda 表达式
  2. 方法引用提供了易读的 lambda 表达式
  3. 接口类的方法提供 default 实现
  4. 重复注解,相同注解可标注多次(java.lang.annotation.Repeatable)
  5. 类型注解(ElementType.TYPE_PARAMETER, ElementType.TYPE_USE)
  6. 改进类型引用
  7. 方法参数反射

2. 集合

  1. java.util.stream 包中的类提供了 Stream API ,支持 对流元素的函数风格操作.
  2. hash key 碰撞性能提升

3. javac

The javac compiler has a -profile option

4. Security

  1. 客户端默认开启 TLS 1.2
  2. 新增AccessController.doPrivileged(PrivilegedExceptionAction<T> action, AccessControlContext context, Permission... perms) throws PrivilegedActionException 能够代码断言 特权的子集,不用遍历stack去校验其他权限.
  3. 更强大的密码加密算法
  4. SSL/TLS Server Name Indication (SNI) Extension support in JSSE Server(不懂)
  5. 支持 AEAD 算法
  6. KeyStore 增强,
  7. SHA-224 Message Digests
  8. 支持 NSA Suite B Cryptography
  9. 更好的支持高级加密随机数字生成.
  10. 新增 java.security.cert.PKIXRevocationChecker 类配置 废弃X.509证书校验
  11. 支持 windows系统 64-bit PKCS11
  12. 新的等级类型-(Kerberos 是由 MIT 设计的一个安全认证协议, )
  13. 支持 Kerberos 5 协议转换和受限委派
  14. 默认关闭 Kerberos 5 弱密码加密类型
  15. 无约束的简单认证与安全层(SASL)
  16. 多主机的SASL service
  17. JNI bridge to native JGSS on Mac OS X
  18. Support for stronger strength ephemeral DH keys in the SunJSSE provider
  19. Support for server-side cipher suites preference customization in JSSE

5. JavaFX

  1. 深紫色主题实现
  2. SwingNode 类允许开发者在 JavaFX 应用中嵌入 Swing
  3. 新的UI控制: DatePicker and the TreeTableView
  4. javafx.print 包提供了打印API
  5. 支持 3D Graphics 特性
  6. WebView 类提供了新特性和改进
  7. 增强 text 支持
  8. 支持 Hi-DPI displays
  9. CSS Styleable 类成功公共API
  10. ScheduledService 类允许重启服务
  11. JavaFX 可在 ARM 平台使用.

6.Tools

  1. jjs 命令调用Nashorn 引擎(一种JavaScript 引擎,已废弃)
  2. java 命令可启动 javaFX 应用
  3. java命令手册已经重写
  4. jdeps 命令分析类文件
  5. Java Management Extensions (JMX) 提供对 诊断命令的远程访问.
  6. The jarsigner tool has an option for requesting a signed time stamp from a Time Stamping Authority (TSA).

7. Internationalization

  1. 支持 Unicode 6.2.0
  2. Adoption of Unicode CLDR Data and the java.locale.providers System Property
  3. 新的 Calendar and Locale APIs
  4. 安装 Custom Resource Bundle 作为扩展

8. Deployment

  1. For sandbox applets and Java Web Start applications, URLPermission is now used to allow connections back to the server from which they were started. SocketPermission is no longer granted.
  2. The Permissions attribute is required in the JAR file manifest of the main JAR file at all security levels.
  3. Date-Time Package - a new set of packages that provide a 全面的 date-time model.

9. Scripting

Nashorn Javascript Engine 替代 Rhino javascript engine

10. Pack200

  1. Pack200 支持常量池和字节码(JSR 292)
  2. JDK8 支持的类文件已经修改(JSR-292, JSR-308 and JSR-335 声明的那些类文件)

11.IO and NIO

  1. 为 Solaris 提供 新的SelectorProvider 实现
  2. 减少 <JDK_HOME>/jre/lib/charsets.jar 包大小
  3. java.lang.String(byte[], *) constructor and thejava.lang.String.getBytes()method 性能提升

12. java.lang and java.util Packages

  1. Parallel Array Sorting
  2. Standard Encoding and Decoding Base64
  3. 支持 无符号算术

13. JDBC

  1. 移除 JDBC-ODBC Bridge
  2. JDBC 4.2 介绍了新特性

14. Java DB

JDK 8 包含了 Java DB 10.10.

15. Networking

  1. 新增 java.net.URLPermission
  2. java.net.HttpURLConnection类中,如果安装了 security manager ,那么需要权限才能调用请求

16.Concurrency

  1. java.util.concurrent包新增了类和接口
  2. java.util.concurrent.ConcurrentHashMap新增了方法支持 聚集操作
  3. java.util.concurrent.atomic 新增类
  4. java.util.concurrent.ForkJoinPool新增方法支持 common pool
  5. '新增 java.util.concurrent.locks.StampedLock

17.Java XML - JAXP

18. HotSpot

  1. 硬件内部函数新增 AES ,使用 选项开启.
-XX:+UseAES -XX:+UseAESIntrinsics
  1. 移除PermGen,永久代

  2. 方法调用字节码指令 支持 默认方法.

JDK 和 JRE 关系

关系图如下:
image

原图地址:https://docs.oracle.com/javase/8/docs/index.html

Java 语言

面向对象编程概念(Object-Oriented Programming Concepts)

1. 什么是对象?

对象是理解面向对象技术的关键.
真实世界的对象有2个特征: 状态(state)和行为(behavior).

软件对象和真实世界对象很相似.都是由2部分组成: state 和 关联的behavior.
对象使用 field 存储state , 通过 methods 暴露行为, Methods 操作 对象内部的state,服务于对象之间的通信,
如下图:
image

2. 什么是类?

Object 是class 的一个实例.类是从对象中抽象出来的.将许多对象的共同的属性(state) 和方法()抽取出来形成1个 class.

class Bicycle {

    int cadence = 0;
    int speed = 0;
    int gear = 1;

    void changeCadence(int newValue) {
         cadence = newValue;
    }

    void changeGear(int newValue) {
         gear = newValue;
    }

    void speedUp(int increment) {
         speed = speed + increment;   
    }

    void applyBrakes(int decrement) {
         speed = speed - decrement;
    }

    void printStates() {
         System.out.println("cadence:" +
             cadence + " speed:" + 
             speed + " gear:" + gear);
    }
}

3. 什么是继承?

面向对象编程允许一个类去继承另一个类的state and behavior 去使用.每个 类只能有1个父类,每个父类可以有多个子类.

image

继承是通过关键字extends实现的.

// MountainBike 继承了 Bicycle
class MountainBike extends Bicycle {

    // new fields and methods defining 
    // a mountain bike would go here

}

4. 什么是接口?

接口就是相关的方法(没有方法体)集合.

interface Bicycle {

    //  wheel revolutions per minute
    void changeCadence(int newValue);

    void changeGear(int newValue);

    void speedUp(int increment);

    void applyBrakes(int decrement);
}

实现接口通过关键字 implements 体现.

// ACMEBicycle 实现了 Bicycle 接口
class ACMEBicycle implements Bicycle {

    int cadence = 0;
    int speed = 0;
    int gear = 1;

   // The compiler will now require that methods
   // changeCadence, changeGear, speedUp, and applyBrakes
   // all be implemented. Compilation will fail if those
   // methods are missing from this class.

    void changeCadence(int newValue) {
         cadence = newValue;
    }

    void changeGear(int newValue) {
         gear = newValue;
    }

    void speedUp(int increment) {
         speed = speed + increment;   
    }

    void applyBrakes(int decrement) {
         speed = speed - decrement;
    }

    void printStates() {
         System.out.println("cadence:" +
             cadence + " speed:" + 
             speed + " gear:" + gear);
    }
}

接口形成了类与外界的连接,这种连接在构建时期由编译器执行.一个类如果实现接口,那么接口里的所有方法必须实现.

Java 8 支持接口类中的方法提供默认实现.

5. 什么是包?

包是组织一组相关类和接口的命名空间.类似于电脑上的文件夹.

Java 8 API 文档:https://docs.oracle.com/javase/8/docs/api/index.html

Java语言基础

变量

Java 编程语言提供了以下几种变量:

  1. 实例变量 Instance Variables (Non-Static Fields)
    成员变量(无 static 关键字修饰)

  2. 类变量 Class Variables (Static Fields)
    static 关键字修饰的 成员变量

  3. 本地变量 Local Variables
    方法内部声明的变量

  4. 参数变量 Parameters
    方法的参数变量

命名

变量名:以字母,$ 符号或下划线(_) 开头.首字母之后的字符可以是字母.数字,$_

变量名最好用全称,别用简写!

一个单词的变量名使用全部小写;第2个单词和以后的单词的首字母需要大写,其余小写;
常量多个字母间使用下划线(_)拼接,所有字母大写.

基本数据类型

  1. byte
    8-bit, 取值范围 -128 到 127(包含127) ( -2的 7次方 到 2的 7次方-1)

  2. short
    16-bit,2个字节, 取值范围- 32,768 到 32,767 (包含32767)

  3. int
    32-bit , 4个字节, 取值范围 -2的 31次方 到 2的 31次方-1

  4. long
    64-bit , 8个字节, 取值范围 -2的 63次方 到 2的 63次方-1

  5. float
    单精度浮点类型,32-bit (如果需要更高精度,则 可使用 java.math.BigDecimal 替换)

  6. double
    双精度浮点类型,64-bit(如果需要更高精度,则 可使用 java.math.BigDecimal 替换)

  7. boolean
    true 或 false

  8. char
    16-bit Unicode 字符, \u0000\uffff

默认值
image

数组

下图是有10个元素的数组
image

声明一个数组:

// declares an array of integers
int[] anArray;

实例化一个数组:

int[] anArray = new int[10]; 

实例化一个数组,并初始化元素值

int[] anArray = {1,2,3,4,5,6,7,8,9,10};

拷贝数组:

class ArrayCopyDemo {
    public static void main(String[] args) {
        String[] copyFrom = {
            "Affogato", "Americano", "Cappuccino", "Corretto", "Cortado",   
            "Doppio", "Espresso", "Frappucino", "Freddo", "Lungo", "Macchiato",      
            "Marocchino", "Ristretto" };
        
        String[] copyTo = new String[7];
        System.arraycopy(copyFrom, 2, copyTo, 0, 7);
        for (String coffee : copyTo) {
            System.out.print(coffee + " ");           
        }
    }
} 

遍历数组:

class ArrayCopyOfDemo {
    public static void main(String[] args) {
        String[] copyFrom = {
            "Affogato", "Americano", "Cappuccino", "Corretto", "Cortado",   
            "Doppio", "Espresso", "Frappucino", "Freddo", "Lungo", "Macchiato",      
            "Marocchino", "Ristretto" };
        
        String[] copyTo = java.util.Arrays.copyOfRange(copyFrom, 2, 9);        
        for (String coffee : copyTo) {
            System.out.print(coffee + " ");           
        }            
    }
}

java.util.Arrays 工具类提供了操作数组的方法,比如:

  1. binarySearch 方法查找元素
  2. equals 比较两个数组
  3. fill 使用特定值填充数组每个元素
  4. sort按升序排列数组
  5. stream 使用流遍历处理数组
java.util.Arrays.stream(copyTo).map(coffee -> coffee + " ").forEach(System.out::print);  
  1. toString将数组转为 String

Java的关键字

  • abstract
  • continue
  • for
  • new
  • switch
  • assert***(added in 1.4)
  • default
  • goto*(not used)
  • package
  • synchronized
  • boolean
  • do
  • if
  • private
  • this
  • break
  • double
  • implements
  • protected
  • throw
  • byte
  • else
  • import
  • public
  • throws
  • case
  • enum****(added in 5.0)
  • instanceof
  • return
  • transient
  • catch
  • extends
  • int
  • short
  • try
  • char
  • final
  • interface
  • static
  • void
  • class
  • finally
  • long
  • strictfp**(added in 1.2)
  • volatile
  • const* (not used)
  • float
  • native
  • super
  • while

注意:

* not used
** added in 1.2
*** added in 1.4
**** added in 5.0

操作符

image
图片地址:https://docs.oracle.com/javase/tutorial/java/nutsandbolts/operators.html

表达式,语句和代码块

表达式 是由 变量,操作符和方法调用组成.

(x + y) / 100  // unambiguous, recommended

语句 : 完整的执行单元,以分号结尾的

表达式语句:

// assignment statement
aValue = 8933.234;
// increment statement
aValue++;
// method invocation statement
System.out.println("Hello World!");
// object creation statement
Bicycle myBike = new Bicycle();

代码块:由一组大括号包含的0到多个语句组成.

class BlockDemo {
     public static void main(String[] args) {
          boolean condition = true;
          if (condition) { // begin block 1
               System.out.println("Condition is true.");
          } // end block one
          else { // begin block 2
               System.out.println("Condition is false.");
          } // end block 2
     }
}

流程控制

  • if-then-else
  • switch ( 1.7 开始,支持String)
public static int getMonthNumber(String month) {

        int monthNumber = 0;

        if (month == null) {
            return monthNumber;
        }

        switch (month.toLowerCase()) {
            case "january":
                monthNumber = 1;
                break;
            case "february":
                monthNumber = 2;
                break;
            case "march":
                monthNumber = 3;
                break;
            case "april":
                monthNumber = 4;
                break;
            case "may":
                monthNumber = 5;
                break;
            case "june":
                monthNumber = 6;
                break;
            case "july":
                monthNumber = 7;
                break;
            case "august":
                monthNumber = 8;
                break;
            case "september":
                monthNumber = 9;
                break;
            case "october":
                monthNumber = 10;
                break;
            case "november":
                monthNumber = 11;
                break;
            case "december":
                monthNumber = 12;
                break;
            default: 
                monthNumber = 0;
                break;
        }

        return monthNumber;
    }
}
  • while 和 do ... while
while (expression) {
     statement(s)
}

do {
     statement(s)
} while (expression);
  • for 循环
for (initialization; termination;
     increment) {
    statement(s)
}

class EnhancedForDemo {
    public static void main(String[] args){
         int[] numbers = 
             {1,2,3,4,5,6,7,8,9,10};
         for (int item : numbers) {
             System.out.println("Count is: " + item);
         }
    }
}
  • break (跳出循环)
  • continue(跳过本次循环,继续下次循环)
  • return(返回)
// 有返回值方法
return ++count;

// void 方法
return;

类和对象

声明一个类:

/*
修饰符 class关键字 类名 extends 父类名 implements 接口名(多个){

}
*/
class MyClass extends MySuperClass implements YourInterface {
    // field, constructor, and
    // method declarations
}

声明成员变量:

// 访问修饰符 类型 变量名
public int cadence;
public int gear;
public int speed;

定义方法:

/*
访问修饰符 返回值 方法名(参数类型 参数名, ...){

}
*/
public double calculateAnswer(double wingSpan, int numberOfEngines,
                              double length, double grossTons) {
    //do the calculation here
}

方法重载:
Java 可以通过方法签名判断方法是否不同,Java 支持一个类中存在方法名相同,参数列表不同的方法.参数列表不同的含义是:方法参数类型不同 或者 方法参数个数不同

//  存在多个重载方法的draw 方法
public class DataArtist {
    ...
    public void draw(String s) {
        ...
    }
    public void draw(int i) {
        ...
    }
    public void draw(double f) {
        ...
    }
    public void draw(int i, double f) {
        ...
    }
}

注意:尽量克制使用方法重载,过多的使用会降低可读性.

构造器
一个类可以包含多个构造器,构造器在创建类的一个对象时被调用.

如果不声明构造器,那么编译器会自动生成一个无参的构造器.

构造器和普通方法很像,但是,构造器不能有返回类型且构造器名必须和类名一致!

/*

修饰符 类名(参数列表...){
     构造器方法体
}
*/

public Bicycle(int startCadence, int startSpeed, int startGear) {
    gear = startGear;
    cadence = startCadence;
    speed = startSpeed;
}

任意数量的参数

//  polygonFrom方法有任意数量且类型为Point的变量
public Polygon polygonFrom(Point... corners) {
    int numberOfSides = corners.length;
    double squareOfSide1, lengthOfSide1;
    squareOfSide1 = (corners[1].x - corners[0].x)
                     * (corners[1].x - corners[0].x) 
                     + (corners[1].y - corners[0].y)
                     * (corners[1].y - corners[0].y);
    lengthOfSide1 = Math.sqrt(squareOfSide1);

    // more method body code follows that creates and returns a 
    // polygon connecting the Points
}

基本类型参数传递的是具体值;引用类型传递的是引用地址.

this关键字
在一个实例的方法或构造器中, this关键字是一个当前对象的引用.

成员变量:

public class Point {
    public int x = 0;
    public int y = 0;
        
    //constructor
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

使用构造器:

public class Rectangle {
    private int x, y;
    private int width, height;
        
    public Rectangle() {
        this(0, 0, 1, 1);
    }
    public Rectangle(int width, int height) {
        this(0, 0, width, height);
    }
    public Rectangle(int x, int y, int width, int height) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }
}

类成员变量的访问级别
有2种类型的访问级别:

  • 类的访问级别: public 或者 默认(只能同一个包内类访问)
  • 成员的访问级别: public, private,protected 或者默认(只能同一个包内类可访问)

image

理解类 成员

  • 类变量: static 修饰的成员变量
  • 类方法: static 修饰的方法
  • 类方法可直接访问类变量和类方法.
  • 类方法不能直接访问非static成员变量和非static方法,只能通过对象.方法访问.

常量

// 常量命名:字母必须全部大写,且多个单词用下划线连接
static final double PI = 3.141592653589793;

初始化成员

public class BedAndBreakfast {

    // initialize to 10
    public static int capacity = 10;

    // initialize to false
    private boolean full = false;
}

静态初始化块
多个静态初始化块,按声明的先后顺序进行初始化.

static {
    // whatever code is needed for initialization goes here
}

或者

class Whatever {
    public static varType myVar = initializeClassVariable();
        
    private static varType initializeClassVariable() {

        // initialization code goes here
    }
}

初始化块
java 编译器,将初始化块复制到每个构造器中

{
    // whatever code is needed for initialization goes here
}

final 方法:

class Whatever {
    private varType myVar = initializeInstanceVariable();
	// final 方法,不能被子类重写
    protected final varType initializeInstanceVariable() {

        // initialization code goes here
    }
}

内部类

class OuterClass {
    ...
	// 内部类
    class InnerClass {
        ...
    }
	// 静态内部类
    static class StaticNestedClass {
        ...
    }
}

内部类 实例化:

// 先实例化外部类,再实例化内部类
OuterClass outerObject = new OuterClass();
OuterClass.InnerClass innerObject = outerObject.new InnerClass();
OuterClass.java
public class OuterClass {

    String outerField = "Outer field";
    static String staticOuterField = "Static outer field";

    class InnerClass {
        void accessMembers() {
            System.out.println(outerField);
            System.out.println(staticOuterField);
        }
    }

    static class StaticNestedClass {
        void accessMembers(OuterClass outer) {
            // Compiler error: Cannot make a static reference to the non-static
            //     field outerField
            // System.out.println(outerField);
            System.out.println(outer.outerField);
            System.out.println(staticOuterField);
        }
    }

    public static void main(String[] args) {
        System.out.println("Inner class:");
        System.out.println("------------");
        OuterClass outerObject = new OuterClass();
        OuterClass.InnerClass innerObject = outerObject.new InnerClass();
        innerObject.accessMembers();

        System.out.println("\nStatic nested class:");
        System.out.println("--------------------");
        StaticNestedClass staticNestedObject = new StaticNestedClass();        
        staticNestedObject.accessMembers(outerObject);
        
        System.out.println("\nTop-level class:");
        System.out.println("--------------------");
        TopLevelClass topLevelObject = new TopLevelClass();        
        topLevelObject.accessMembers(outerObject);                
    }
}

public class TopLevelClass {

    void accessMembers(OuterClass outer) {     
        // Compiler error: Cannot make a static reference to the non-static
        //     field OuterClass.outerField
        // System.out.println(OuterClass.outerField);
        System.out.println(outer.outerField);
        System.out.println(OuterClass.staticOuterField);
    }  
}

内部类的序列化强烈不推荐使用.

本地类
可以在任意一个代码块中,声明一个类.

public class LocalClassExample {
  
    static String regularExpression = "[^0-9]";
  
    public static void validatePhoneNumber(
        String phoneNumber1, String phoneNumber2) {
      
        final int numberLength = 10;
        
        // Valid in JDK 8 and later:
       
        // int numberLength = 10;
       
        class PhoneNumber {
            
            String formattedPhoneNumber = null;

            PhoneNumber(String phoneNumber){
                // numberLength = 7;
                String currentNumber = phoneNumber.replaceAll(
                  regularExpression, "");
                if (currentNumber.length() == numberLength)
                    formattedPhoneNumber = currentNumber;
                else
                    formattedPhoneNumber = null;
            }

            public String getNumber() {
                return formattedPhoneNumber;
            }
            
            // Valid in JDK 8 and later:

//            public void printOriginalNumbers() {
//                System.out.println("Original numbers are " + phoneNumber1 +
//                    " and " + phoneNumber2);
//            }
        }

        PhoneNumber myNumber1 = new PhoneNumber(phoneNumber1);
        PhoneNumber myNumber2 = new PhoneNumber(phoneNumber2);
        
        // Valid in JDK 8 and later:

//        myNumber1.printOriginalNumbers();

        if (myNumber1.getNumber() == null) 
            System.out.println("First number is invalid");
        else
            System.out.println("First number is " + myNumber1.getNumber());
        if (myNumber2.getNumber() == null)
            System.out.println("Second number is invalid");
        else
            System.out.println("Second number is " + myNumber2.getNumber());

    }

    public static void main(String... args) {
        validatePhoneNumber("123-456-7890", "456-7890");
    }
}

匿名类

public class HelloWorldAnonymousClasses {
  
    interface HelloWorld {
        public void greet();
        public void greetSomeone(String someone);
    }
  
    public void sayHello() {
        
        class EnglishGreeting implements HelloWorld {
            String name = "world";
            public void greet() {
                greetSomeone("world");
            }
            public void greetSomeone(String someone) {
                name = someone;
                System.out.println("Hello " + name);
            }
        }
      
        HelloWorld englishGreeting = new EnglishGreeting();
        
        HelloWorld frenchGreeting = new HelloWorld() {
            String name = "tout le monde";
            public void greet() {
                greetSomeone("tout le monde");
            }
            public void greetSomeone(String someone) {
                name = someone;
                System.out.println("Salut " + name);
            }
        };
        
        HelloWorld spanishGreeting = new HelloWorld() {
            String name = "mundo";
            public void greet() {
                greetSomeone("mundo");
            }
            public void greetSomeone(String someone) {
                name = someone;
                System.out.println("Hola, " + name);
            }
        };
        englishGreeting.greet();
        frenchGreeting.greetSomeone("Fred");
        spanishGreeting.greet();
    }

    public static void main(String... args) {
        HelloWorldAnonymousClasses myApp =
            new HelloWorldAnonymousClasses();
        myApp.sayHello();
    }            
}

匿名内部类访问外部类本地变量限制(https://docs.oracle.com/javase/tutorial/java/javaOO/anonymousclasses.html#accessing):

  • 匿名内部类,可以访问所在类的成员变量;
  • 匿名内部类不能访问本地变量(没有final关键字修饰的变量);
  • 匿名内部类声明的变量,覆盖外部内相同名字的变量;
  • 匿名内部类不能声明静态初始化方法或静态成员接口(You cannot declare static initializers or member interfaces in an anonymous class).
  • 匿名内部类可以有静态常量;
  • 不能再匿名内部类中声明构造器

Lambda表达式
语法:

// 格式:  小括号包围的逗号分隔的参数列表 + "->" + 方法体
// Java 会自动评估返回值
p -> p.getGender() == Person.Sex.MALE 
    && p.getAge() >= 18
    && p.getAge() <= 25

// 或者
// p 代表方法参数类型的实例,下面示例的方法只有1个参数
p -> {
    return p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25;
}

注:不推荐序列化 lambda表达式.

lambda方法引用 4种类型:
image

什么时候使用内部类,本地类,匿名类,Lambda表达式?
链接:https://docs.oracle.com/javase/tutorial/java/javaOO/whentouse.html
本地类:当创建一个类的多个实例,访类的构造方法.
匿名内部类:如果需要声明成员变量或额外的方法时使用.
lambda表达式:(1) 当你想将单一的行为传给其他代码去执行 (2) 创建函数接口简单实例时
内部类:和本地类需求一样,但是不要求访问外部类的本地变量和方法参数

对象

创建对象:

Point originOne = new Point(23, 94);

创建对象语句,包含了3部分:
(1) 声明变量:比如上边的 Point originOne.
(2) 实例化对象: new 关键字是创建对象的操作符.

  • 给对象分配内存,并返回内存地址的引用.

(3) 初始化对象: new 操作符后紧跟着调用构造器方法进行初始化对象.

垃圾回收
一些面向对象语言要求你跟踪对象,当对象不再使用的时候进行销毁.
Java 允许你创建足够的对象,不用担心销毁他们,Java 运行环境当发现对象不再使用时,自动删除对象,这样的过程叫垃圾回收.
垃圾收集器自动回收释放对象占用的空间.

枚举类型

enum 类型是一个特殊的数据类型,能够让变量预定义一些常量集合.
enum 类型成员是大写字母.

// 简单枚举
public enum Day {
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
    THURSDAY, FRIDAY, SATURDAY 
}

测试:

public class EnumTest {
    Day day;
    
    public EnumTest(Day day) {
        this.day = day;
    }
    
    public void tellItLikeItIs() {
        switch (day) {
            case MONDAY:
                System.out.println("Mondays are bad.");
                break;
                    
            case FRIDAY:
                System.out.println("Fridays are better.");
                break;
                         
            case SATURDAY: case SUNDAY:
                System.out.println("Weekends are best.");
                break;
                        
            default:
                System.out.println("Midweek days are so-so.");
                break;
        }
    }
    
    public static void main(String[] args) {
        EnumTest firstDay = new EnumTest(Day.MONDAY);
        firstDay.tellItLikeItIs();
        EnumTest thirdDay = new EnumTest(Day.WEDNESDAY);
        thirdDay.tellItLikeItIs();
        EnumTest fifthDay = new EnumTest(Day.FRIDAY);
        fifthDay.tellItLikeItIs();
        EnumTest sixthDay = new EnumTest(Day.SATURDAY);
        sixthDay.tellItLikeItIs();
        EnumTest seventhDay = new EnumTest(Day.SUNDAY);
        seventhDay.tellItLikeItIs();
    }
}

// 带构造器枚举类
public enum Planet {
    MERCURY (3.303e+23, 2.4397e6),
    VENUS   (4.869e+24, 6.0518e6),
    EARTH   (5.976e+24, 6.37814e6),
    MARS    (6.421e+23, 3.3972e6),
    JUPITER (1.9e+27,   7.1492e7),
    SATURN  (5.688e+26, 6.0268e7),
    URANUS  (8.686e+25, 2.5559e7),
    NEPTUNE (1.024e+26, 2.4746e7);

    private final double mass;   // in kilograms
    private final double radius; // in meters
    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
    }
    private double mass() { return mass; }
    private double radius() { return radius; }

    // universal gravitational constant  (m3 kg-1 s-2)
    public static final double G = 6.67300E-11;

    double surfaceGravity() {
        return G * mass / (radius * radius);
    }
    double surfaceWeight(double otherMass) {
        return otherMass * surfaceGravity();
    }
    public static void main(String[] args) {
        if (args.length != 1) {
            System.err.println("Usage: java Planet <earth_weight>");
            System.exit(-1);
        }
        double earthWeight = Double.parseDouble(args[0]);
        double mass = earthWeight/EARTH.surfaceGravity();
        for (Planet p : Planet.values())
           System.out.printf("Your weight on %s is %f%n",
                             p, p.surfaceWeight(mass));
    }
}

注解

注解,元数据(我理解是一个配置信息)的一种形式,提供关于程序的数据,但注解并不属于程序.

Annotations 有很多用途,:

  • 给编译器提供信息,编译器可通过注解发现错误和禁止警告.
  • 编译时期和部署时期处理:软件工具可以根据注解生成代码,xml文件.
  • 运行时期处理:一些注解能够在运行时期检查.

注解用在哪里?

  • 注解应用于声明:类,成员变量,方法和其他程序元素.
  • JAVA 8 支持注解应用在类型使用的地方,比如:
    • 创建类实例表达式
     new @Interned MyObject();
    
    • 类型匹配
     myString = (@NonNull String) str;
    
    • 接口实现
     class UnmodifiableList<T> implements
          @Readonly List<@Readonly T> { ... }
    
    • 异常抛出
     void monitorTemperature() throws
          @Critical TemperatureException { ... }
    

声明一个注解

通过 @interface 来定义一个注解类:

@interface ClassPreamble {
   String author();
   String date();
   int currentRevision() default 1;
   String lastModified() default "N/A";
   String lastModifiedBy() default "N/A";
   // Note use of array
   String[] reviewers();
}
@ClassPreamble (
   author = "John Doe",
   date = "3/17/2002",
   currentRevision = 6,
   lastModified = "4/12/2004",
   lastModifiedBy = "Jane Doe",
   // Note array notation
   reviewers = {"Alice", "Bob", "Cindy"}
)
public class Generation3List extends Generation2List {

// class code goes here

}

Java 提供的预定义注解

  • @Deprecated : 标记的元素已经过时,不再维护
  • @Override : 通知编译器此元素重写父类的元素.
  • @SuppressWarnings :告诉编译器去禁止特定的警告
  • @SafeVarargs:应用于方法或构造器
  • @FunctionalInterface : 表明类型为函数接口类型

元注解
Java 提供的标注在其他注解上的注解

  • @Retention: 表示标记的注解怎么存储,
    • RetentionPolicy.SOURCE 注解仅保留在原代码上,编译器会忽略
    • RetentionPolicy.CLASS:注解保留在编译阶段,JVM会忽略
    • RetentionPolicy.RUNTIME: 注解保留在JVM中,在运行环境中可以使用
  • @Documented : 表示改注解将在Java Doc 中保留
  • @Target : 注解标注的位置
    • ElementType.ANNOTATION_TYPE: 可以标注在注解上
    • ElementType.CONSTRUCTOR:可以标注在构造器上
    • ElementType.FIELD:可以标注在成员变量上
    • ElementType.LOCAL_VARIABLE: 可以标注在本地变量上
    • ElementType.METHOD : 可以标注在方法上
    • ElementType.PACKAGE: 可以标注在包上
    • ElementType.PARAMETER:可以标注在方法参数上
    • ElementType.TYPE:可以标注在类的任意元素上
  • @Inherited:子类可以从父类上标注的注解继承
  • @Repeatable: 同一注解可以在同一位置可以标注多个

Java 8 以前,注解只能在声明的时候使用,现在从 Java 8 开始可以在类使用的地方.

Repeating 注解
可重复注解,使用方式如下:

@Alert(role="Manager")
@Alert(role="Administrator")
public class UnauthorizedAccessException extends SecurityException { ... }

Java 编译器会自动将可重复注解存储到一个容器注解中, 为了让Java 编译器能够实现把可重复注解存到容器注解中,必须在代码里声明以下2个类:

  1. 声明一个可重复注解@Schedule
import java.lang.annotation.Repeatable;
//  Schedules.class 是容器注解类型,用于存储 可重复注解 @Schedule
@Repeatable(Schedules.class)
public @interface Schedule {
  String dayOfMonth() default "first";
  String dayOfWeek() default "Mon";
  int hour() default 12;
}
  1. 声明一个容器注解 @Schedules
public @interface Schedules {
	// 必须存在一个 value 属性,类型为 可重复注解
    Schedule[] value();
}

以上,是指定了容器注解类型,但是如果没有指定,那么 会报错.

例子:

获取标注的注解示例:

// 通过  
 AnnotatedElement.getAnnotationsByType(Class<T>)

测试:


import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * <p>注释注解<p/>
 * <p>特点:<p/>
 * <li>仅保留在程序运行阶段(可重复注解的保留阶段应包含 CommentContainer的保留阶段 )</li>
 * <li>可重复注解,多个注解存储在 CommentContainer的 value()中</li>
 * <li>可标注在 class 和 interface上.</li>
 * @author limo
 * @date 2022-01-10 18:43:027
 */
@Retention(RetentionPolicy.CLASS)
@Repeatable(CommentContainer.class)
@Target({ElementType.TYPE})
public @interface Comment {

    // 作者 
    String auth();
    // 日期 
    String date();
    // 版本
    String version();
}


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 
 * {@code @Comment} 注解的容器注解,用于存储多个  {@code @Comment} 注解的数据
 * <p>特点:<p/>
 * <li>仅保留在程序运行阶段(可重复注解的保留阶段应包含 CommentContainer的保留阶段 )</li>
 * <li>可标注在 class 和 interface上.(需包含 可重复注解{@code @Comment} 的@Target)</li>
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.ANNOTATION_TYPE,ElementType.TYPE})
public @interface CommentContainer {

    // 必须有 value 属性,其存储类型为可重复注解@Comment的数组
    Comment[] value(); 
}
@Comment(auth = "limo", date = "2021-01-10", version = "v1.0版本")
@Comment(auth = "black", date = "2022-02-20", version = "v2.0版本")
public class Demo {

    public static void main(String[] args) {

	Class demoClass = Demo.class;
	// 通过 getAnnotation(CommentContainer.class) 获取标注的 @Comment 注解的值
	// 如果标注多个 @Comment 注解,此方法会报异常
	try {
	    System.out.println("================demoClass.getAnnotation(Comment.class)===============================");
	    Comment limoComment = (Comment) demoClass.getAnnotation(Comment.class);
	    System.out.println("demo 的注释:");
	    System.out.println("\t作者:" + limoComment.auth());
	    System.out.println("\t时间:" + limoComment.date());
	    System.out.println("\t版本:" + limoComment.version());
	} catch (Exception e) {
	    e.printStackTrace();
	}
	try {
	    Thread.sleep(1L);
	} catch (InterruptedException e1) {
	    e1.printStackTrace();
	}

	// 通过 getAnnotation(CommentContainer.class) 获取标注的每个 @Comment
	// 如果有 1个 @Comment注解,,此方法会报异常
	System.out.println(
		"================demoClass.getAnnotation(CommentContainer.class)===============================");
	try {
	    CommentContainer commentContainer = (CommentContainer) demoClass.getAnnotation(CommentContainer.class);
	    Comment[] commentContainerValue = commentContainer.value();
	    for (Comment c : commentContainerValue) {
		System.out.println("demo 的注释:");
		System.out.println("\t作者:" + c.auth());
		System.out.println("\t时间:" + c.date());
		System.out.println("\t版本:" + c.version());
	    }

	} catch (Exception e) {
	    e.printStackTrace();
	}
	try {
	    Thread.sleep(1L);
	} catch (InterruptedException e1) {
	    e1.printStackTrace();
	}

	// getAnnotationsByType(Comment.class) 获取标注的每个 @Comment 注解
	// 如果有没有@Comment注解或者多个@Comment注解,此方法不会报异常
	System.out.println(
		"================demoClass.getAnnotationsByType(Comment.class)===============================");
	try {
	    Comment[] comments = (Comment[]) demoClass.getAnnotationsByType(Comment.class);
	    for (Comment c : comments) {
		System.out.println("demo 的注释:");
		System.out.println("\t作者:" + c.auth());
		System.out.println("\t时间:" + c.date());
		System.out.println("\t版本:" + c.version());
	    }
	} catch (Exception e) {
	    e.printStackTrace();
	}
    }
}

测试结果:

================demoClass.getAnnotation(Comment.class)===============================
 demo 的注释:
java.lang.NullPointerException
	at com.untouch.hdmap.uaa.annotation.Demo.main(Demo.java:16)
================demoClass.getAnnotation(CommentContainer.class)===============================
 demo 的注释:
	作者:limo
	时间:2021-01-10
	版本:v1.0版本
 demo 的注释:
	作者:black
	时间:2022-02-20
	版本:v2.0版本
================demoClass.getAnnotationsByType(Comment.class)===============================
 demo 的注释:
	作者:limo
	时间:2021-01-10
	版本:v1.0版本
 demo 的注释:
	作者:black
	时间:2022-02-20
	版本:v2.0版本

经测试,我推荐使用 getAnnotationsByType(Comment.class)代码获取标注的多个@Comment注解时, 不管你有没有使用@Comment注解都不会报异常.

灵活使用 @Repeatable 注解和 @Target 能设计出更灵活的注解.

接口与继承

接口

定义接口

使用关键子 interface 定义一个接口类

public interface GroupedInterface extends Interface1, Interface2, Interface3 {

	// 接口体可以包含: 抽象方法,默认方法,静态方法
    // constant declarations
    
    // base of natural logarithms
    double E = 2.718282;
 
    // method signatures(无方法体)
    void doSomething (int i, double x);
    int doSomethingElse(String s);
}

一个类通过 implements 关键字来实现接口.

public interface Relatable {
        
    // this (object calling isLargerThan)
    // and other must be instances of 
    // the same class returns 1, 0, -1 
    // if this is greater than, 
    // equal to, or less than other
    public int isLargerThan(Relatable other);
}


public class RectanglePlus 
    implements Relatable {
    public int width = 0;
    public int height = 0;
    public Point origin;

    // four constructors
    public RectanglePlus() {
        origin = new Point(0, 0);
    }
    public RectanglePlus(Point p) {
        origin = p;
    }
    public RectanglePlus(int w, int h) {
        origin = new Point(0, 0);
        width = w;
        height = h;
    }
    public RectanglePlus(Point p, int w, int h) {
        origin = p;
        width = w;
        height = h;
    }

    // a method for moving the rectangle
    public void move(int x, int y) {
        origin.x = x;
        origin.y = y;
    }

    // a method for computing
    // the area of the rectangle
    public int getArea() {
        return width * height;
    }
    
    // a method required to implement
    // the Relatable interface
    public int isLargerThan(Relatable other) {
        RectanglePlus otherRect 
            = (RectanglePlus)other;
        if (this.getArea() < otherRect.getArea())
            return -1;
        else if (this.getArea() > otherRect.getArea())
            return 1;
        else
            return 0;               
    }
}

接口扩展
如果接口设计的不合理,需要新增方法,那么接口的双方都需要重新调整接口;可通过新增接口继承原接口,并添加新增的方法扩展接口:

// 源接口
public interface DoIt {
   void doSomething(int i, double x);
   int doSomethingElse(String s);
}

// 新接口,扩展了源接口,并新增 didItWork 方法
public interface DoItPlus extends DoIt {

   boolean didItWork(int i, double x, String s);
   
}

还可以新增 default 方法,代码如下:

public interface DoIt {

   void doSomething(int i, double x);
   int doSomethingElse(String s);
   // 默认方法,接口实现类并不需要覆盖
	default boolean didItWork(int i, double x, String s) {
       // Method body 
   }
   
}

测试

/**
 * TV接口
 * @author black
 * @date 2022-01-11 12:16:034
 */
public interface TV {

	// 名字
	String name();
	// 生产日期
	String date();
	// 信息
	default void print() {
		System.out.println("品牌:未知");
	}
}


/**
 * 小米电视
 * @author black
 * @date 2022-01-11 12:16:008
 */
public class MiTV implements TV{

	@Override
	public String name() {
		return "MIA40TV";
	}

	@Override
	public String date() {
		return "2022-01-11 12:17:57";
	}

	public void print() {
		System.out.println("品牌:小米TV");
		// 如果想使用接口中定义的默认方法,则 使用 TV.super.print()调用
	}
}


/**
 * 测试类
 * @author black
 * @date 2022-01-11 12:15:056
 */
public class TVTest {

	public static void main(String[] args) {
		TV t = new MiTV();
		t.print();
	}
	
}


测试结果:

品牌:小米TV

默认方法可以覆盖:

/**
 * TV接口
 * @author black
 * @date 2022-01-11 12:16:034
 */
public interface TV {

	// 名字
	String name();
	// 生产日期
	String date();
	// 信息
	default void print() {
		System.out.println("品牌:未知");
	}
}


public interface TVPlus extends TV {

	// 重写 父接口的 name方法
	@Override
	default String name(){
		System.out.println("TvPlus");
	}
	// 覆盖父接口的 print 默认方法
	@Override
	default void print() {
		// TODO Auto-generated method stub
		System.out.println("TvPlus");
	}
}
继承

从原始类衍生出来的类叫做子类,原始类就叫父类(超类,基类).

子类继承父类所有所有(非私)成员(除了构造器).

java.lang.Object 类定义了所有类的通用行为的实现,Java 平台中所有类都是 Object类的后代.

image

类型猜测

// 父类声明指向子类实现
Object obj = new MountainBike();

// 如何判断并获取子类对象实例?
(1)  instanceof 判断
(2) 强转
if (obj instanceof MountainBike) {
    MountainBike myBike = (MountainBike)obj;
}

一个类只能继承1个父类,但是可以实现多个接口.

多态
父类声明指向子类实例,同一行为的表现结果不一样.

super关键字
super 代表父类实例,通过 super 关键字调用父类的属性和方法.

Object 类方法说明

  • protected Object clone() throws CloneNotSupportedException
    创建并返回这个对象的一个实例.
  • public boolean equals(Object obj)
    其他对象是否和当前对象相等
  • protected void finalize() throws Throwable
    垃圾收集器在发现此对象没人引用时调用
  • public final Class getClass()
    返回这个对象运行时的类
  • public int hashCode()
    返回当前对象的 hash code
  • public String toString()
    返回对象的字符串表示

notify() , notifyAll(), wait(),wait(long timeout) 方法跟 synchronize 同步器配合使用.

final关键字
final 修饰的类不允许子类继承,修饰的方法不允许子类重写.

抽象类
使用 abstract 关键字定义一个抽象类或抽象方法.
抽象类不能被实例化, 必须指向子类实例. abstract 关键字不能修饰变量.

public abstract class GraphicObject {
   // declare fields
   // declare nonabstract methods
	// 抽象方法可以没有方法体,其子类必须实现此方法.
   abstract void draw();
}

如果抽象类被继承,那么抽象类所有的抽象方法,子类必须实现全部.

接口里的方法可以被修饰为 abstract 修饰,但是没有必要,因为接口里的方法,可以没有方法体.

抽象类和接口比较
什么时候该使用抽象类?

  • 几个紧密相关的类之间分享相同代码
  • 扩展自抽象类的类具有很多通用属性和方法,使用非public访问修饰符
  • 你想声明非静态且非final变量,

什么时候该使用接口?

  • 你期望没有关系的类实现接口
  • 你想指定一个特定数据类型的行为,但是不知道谁会实现它
  • 使用接口继承的优势

抽象类实现接口

abstract class X implements Y {
  // 可仅实现 Y接口的一个方法
}

class XX extends X {
  // 实现Y剩余的方法
}

Number 与String

Number

Java 为了使用对象替换基本类型,提供了包装类.
所有包装类的父类都是 Number 类的子类:
image

格式化输出

  1. java.io.PrintStream

public PrintStream format(String format, Object... args) 方法进行格式化输出.System.out 其实就是 PrintStream类.

格式字符串语法文档: https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax

格式字符串有以下语法:
(1) 一般格式化(字符,数值格式化)语法:
%[argument_index$][flags][width][.precision]conversion

  • argument_index:参数列表中对应参数的位置, '1\('代表第一个参数,'2\)'代表第2个参数
  • flags:字符串集合,用于调整格式化输出,依赖于 conversion
  • width:指明输出字符数量的最小数量
  • .precision: 限制字符的数量 ,依赖于 conversion
  • conversion:一个字符,用于指明参数应该被怎么格式化. 参见下面的 <Conversion 列表>

(2) 针对于日期和时间格式化语法:
%[argument_index$][flags][width]conversion

  • argument_index:参数列表中对应参数的位置, '1\('代表第一个参数,'2\)'代表第2个参数
  • flags:字符串集合,用于调整格式化输出,依赖于 conversion
  • width:指明输出字符数量的最小数量
  • conversion: 参见下面的 <Conversion 列表>

(3)没有参数列表的格式化语法:
%[flags][width]conversion

  • flags:字符串集合,用于调整格式化输出,依赖于 conversion
  • width:指明输出字符数量的最小数量
  • conversion: 参见下面的 <Conversion 列表>

Conversion 列表:

转换 参数类别 描述
'b', 'B' general 如果参数为null,则返回"false" ;如果参数是布尔类型,则返回String.valueOf(arg);否则,返回"true"
'h', 'H' general 如果参数为null,则返回"null" ; 否则结果是 Integer.toHexString(arg.hashCode())
's', 'S' general 如果参数为null,则返回"null" ; 如果 参数实现了Formattable 接口,则调用参数的 formatTo方法;否则调用参数的 toString()方法
'c', 'C' general Unicode 字符
'd' general 结果被格式化为十进制整数
'o' general 结果被格式化为 八进制 整型
'x', 'X' general 结果被格式化为 十六进制 整型
'e', 'E' general 结果被格式化为计算机科学记数法中的十进制数
'f' general 结果被格式化为浮点型
'g', 'G' general 结果采用计算机化的科学记数法或十进制格式,取决于精度和四舍五入的值
'a', 'A' general 结果被格式化为具有有效值和指数的十六进制浮点数 ,不支持BigDecimal
't', 'T' date/time 日期转换前缀,参见日期转换列表
'%' percent 百分号
'n' general 行分割符,跟平台有关

时间转换符

转换 说明
'H' 24小时格式,00 ,01,02,03,..., 23
'I'(大写 哎) 12小时格式,01 ~ 12
'k' 24小时格式,0,1,2,3... ~ 23
'l' (小写诶哦) 12小时格式,1 ~ 12
'M' 分钟 00 ~ 19
'S' 秒 00 ~ 60
'L' 毫秒 000 ~ 999
'N' 纳秒 000000000 ~ 999999999
'p' 上午下午 am 或 pm
'z' RFC 822 style numeric time zone offset from GMT, e.g. -0800.
'Z' 表示时区缩写的字符串
's' 从 1970年1月1日 00:00:00 到现在的秒数, Long.MIN_VALUE/1000 to Long.MAX_VALUE/1000
'Q' 从 1970年1月1日 00:00:00 到现在的毫秒数, Long.MIN_VALUE to Long.MAX_VALUE

日期转换符

转换符 说明
'B' 月,英文单词全拼
'b' 月,英文单词缩写
'h' 月,英文单词缩写
'A' 周一周二等的英文单词全拼,Sunday
'a' 周一周二等的英文单词全拼,Sun
'C' 4位年整除 100后的值,00~99
'Y' 年 (4位)
'y' 4位年最后2位
'j' 年的天,001 - 366
'm' 月,01 - 13
'd' 月的天 01 - 31
'e' 月的天 1 - 31

Flag 列表

flag 说明
'-' 左对齐
'#' 结果应该使用依赖于转换的替代形式
'+' 结果总包含一个符号
' ' 结果将包括一个前导空格以表示正值
'0' 结果将用零填充
',' 结果将包括特定于区域设置的分组分隔符
'(' 结果将在括号中包含负数
  1. java.text.DecimalFormat
import java.text.*;

public class DecimalFormatDemo {

   static public void customFormat(String pattern, double value ) {
      DecimalFormat myFormatter = new DecimalFormat(pattern);
      String output = myFormatter.format(value);
      System.out.println(value + "  " + pattern + "  " + output);
   }

   static public void main(String[] args) {

      customFormat("###,###.###", 123456.789);
      customFormat("###.##", 123456.789);
      customFormat("000000.000", 123.78);
      customFormat("$###,###.###", 12345.67);  
   }
}

image

自动装箱拆箱
基本类型和其包装类可以直接赋值使用,并不进行强转.

// 自动装箱
Boolean b = true;
		Integer i = 1;
		Double d = 0.2d;
		Float f = 0.3f;

// 自动拆箱
		boolean bb = b;
		int ii = i;
		double dd = d;
		float ff = f;

泛型

为什么使用泛型?

  • 强类型校验
  • 消除强转
  • 程序员能够实现泛型算法

泛型类

/**
 * Generic version of the Box class.
 * @param <T> the type of the value being boxed
 */
public class Box<T> {
    // T stands for "Type"
    private T t;

    public void set(T t) { this.t = t; }
    public T get() { return t; }
}

泛型类型命名规则

单个大写字母

常见的泛型类型:

  • E - Element (used extensively by the Java Collections Framework)
  • K - Key
  • N - Number
  • T - Type
  • V - Value
  • S,U,V etc. - 2nd, 3rd, 4th types

调用和实例化泛型类型

Box<Integer> integerBox = new Box<Integer>();

从 JDK7 开始,只要Java 编译器能够根据上下文决定类型,那么就可以使用空菱形<>代替实例化时的类型参数:

Box<Integer> integerBox = new Box<>();

多类型参数的泛型类

public interface Pair<K, V> {
    public K getKey();
    public V getValue();
}

public class OrderedPair<K, V> implements Pair<K, V> {

    private K key;
    private V value;

    public OrderedPair(K key, V value) {
	this.key = key;
	this.value = value;
    }

    public K getKey()	{ return key; }
    public V getValue() { return value; }
}

原生类型
声明和实例化时不指定具体类型,就是原生类型:

// Box 是个泛型类,实例化时并不执行泛型类型,此时被称为原生类型
Box rawBox = new Box();

泛型方法
泛型方法,就是说明了方法参数类型的方法.

public class Util {
	// compare方法参数有2个泛型类型分别是 K和V
    public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
        return p1.getKey().equals(p2.getKey()) &&
               p1.getValue().equals(p2.getValue());
    }
}

泛型的边界
泛型中使用 extends关键字限制使用类型的边界. 泛型extends关键字同时包含继承实现语义.

// 单个界限
public class Box<T> {

    private T t;          

    public void set(T t) {
        this.t = t;
    }

    public T get() {
        return t;
    }

    public <U extends Number> void inspect(U u){
        System.out.println("T: " + t.getClass().getName());
        System.out.println("U: " + u.getClass().getName());
    }

    public static void main(String[] args) {
        Box<Integer> integerBox = new Box<Integer>();
        integerBox.set(new Integer(10));
        integerBox.inspect("some text"); // error: this is still String!
    }
}


多个边界:

// <T extends B1 & B2 & B3>
Class A { /* ... */ }
interface B { /* ... */ }
interface C { /* ... */ }

class D <T extends A & B & C> { /* ... */ }

// 如果  A 不在前边的话,编译会报错
// 比如:class D <T extends B & A & C> { /* ... */ }  // compile-time error

通过限定泛型边界可对泛型参数进行算数:


public static <T extends Comparable<T>> int countGreaterThan(T[] anArray, T elem) {
    int count = 0;
    for (T e : anArray)
		// 如果没有 extends Comparable ,那么 e 和 elem 就无法比较(使用大于小于都不行),编译就会报错
        if (e.compareTo(elem) > 0)
            ++count;
    return count;
}

通配符
在泛型里,问号"?"通配符表示未知类型,

类型擦除
为了实现泛型,Java 编译器进行了以下方面的类型擦除:

  • 将泛型类型中的所有类型参数替换为它们的边界或Object(如果类型参数是无界的)。因此,生成的字节码只包含普通的类、接口和方法。
  • 如果需要,插入类型强制转换以保持类型安全
  • 生成桥接方法以在扩展的泛型类型中保持多态性。

泛型的限制

  • 不能使用基本类型实例化泛型类
class Pair<K, V> {

    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    // ...
}
Pair<int, char> p = new Pair<>(8, 'a');  // compile-time error
  • 不能实例化类型参数
public static <E> void append(List<E> list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}
  • 不能声明静态属性
public class MobileDevice<T> {
    private static T os;

    // ...
}
// 如果允许泛型用在静态变量,那么下面对象中os属性的类型到底是什么?
MobileDevice<Smartphone> phone = new MobileDevice<>();
MobileDevice<Pager> pager = new MobileDevice<>();
MobileDevice<TabletPC> pc = new MobileDevice<>();
  • 不能对参数化类型使用类型转换或instanceof
public static <E> void rtti(List<E> list) {
    if (list instanceof ArrayList<Integer>) {  // compile-time error
        // ...
    }
}
List<Integer> li = new ArrayList<>();
List<Number>  ln = (List<Number>) li;  // compile-time error
  • 不能创建泛型数组
List<Integer>[] arrayOfLists = new List<Integer>[2];  // compile-time error
  • 不能创建、捕获或抛出参数化类型的对象
// Extends Throwable indirectly
class MathException<T> extends Exception { /* ... */ }    // compile-time error

// Extends Throwable directly
class QueueFullException<T> extends Throwable { /* ... */ // compile-time error
  • 不能重载方法,其中每个重载的形式参数类型擦除为相同的原始类型
public class Example {
    public void print(Set<String> strSet) { }
    public void print(Set<Integer> intSet) { }
}

包名命名规则

  1. 全部小写
  2. 域名倒叙命名 比如:com.cnblogs

使用包里的类

  1. 全限定名
// 使用 graphics包下的 Rectangle 类
graphics.Rectangle myRect = new graphics.Rectangle();
  1. 使用import关键字导入后使用
// 
import graphics.Rectangle;
Rectangle rc = new Rectangle();
// 静态导入
import static java.lang.Math.*;

double r = cos(PI * theta);

异常

什么是异常?

异常是程序执行发生的一个扰乱正常流程的事件.

异常调用栈:
image

3种类型的异常:

  • 检查型异常(比如,FileNotFoundException)
  • 错误(Error)
  • 运行时异常(RuntimeException)

使用 try-catch-finally 代码块处理异常.

try {

	 code
} catch (ExceptionType name) {

} catch (ExceptionType name) {

}

当try 包围的代码抛出异常时,会查找相关联的异常处理器.每一个 catch 块是一个异常处理器,catch 捕获的异常类型均继承自 Throwable类.

JDK 1.7 开始 ,catch可同时捕获多个异常:

catch (IOException|SQLException ex) {
    logger.log(ex);
    throw ex;
}

当 try 块退出的的时候,finally块肯定会执行(如果在执行 try块过程中 JVM退出,那么 finally 不会执行.).

finally {
    if (out != null) { 
        System.out.println("Closing PrintWriter");
        out.close(); 
    } else { 
        System.out.println("PrintWriter not open");
    } 
} 

finally 块是防止资源泄露的关键工具.

try-with-resources语句
try-with-resources 就是 一个声明了一个或做个资源的 try 块.任何 实现了 java.lang.AutoCloseable接口且所包含的对象都实现了java.io.Closeable的任何对象都能够被用作一个资源.

static String readFirstLineFromFile(String path) throws IOException {
	// try 声明了一个 br.
	// 从 jdk 1.7 开始,BufferedReader实现了 java.lang.AutoCloseable 接口
    try (BufferedReader br =
                   new BufferedReader(new FileReader(path))) {
        return br.readLine();
    }
}

声明多个资源的try:

 try (
        java.util.zip.ZipFile zf =
             new java.util.zip.ZipFile(zipFileName);
        java.io.BufferedWriter writer = 
            java.nio.file.Files.newBufferedWriter(outputFilePath, charset)
    ) {
        // Enumerate each entry
        for (java.util.Enumeration entries =
                                zf.entries(); entries.hasMoreElements();) {
            // Get the entry name and write it to the output file
            String newLine = System.getProperty("line.separator");
            String zipEntryName =
                 ((java.util.zip.ZipEntry)entries.nextElement()).getName() +
                 newLine;
            writer.write(zipEntryName, 0, zipEntryName.length());
        }
    }

try-catch-finally 示例:

public void writeList() {
    PrintWriter out = null;

    try {
        System.out.println("Entering" + " try statement");

        out = new PrintWriter(new FileWriter("OutFile.txt"));
        for (int i = 0; i < SIZE; i++) {
            out.println("Value at: " + i + " = " + list.get(i));
        }
    } catch (IndexOutOfBoundsException e) {
        System.err.println("Caught IndexOutOfBoundsException: "
                           +  e.getMessage());
                                 
    } catch (IOException e) {
        System.err.println("Caught IOException: " +  e.getMessage());
                                 
    } finally {
        if (out != null) {
            System.out.println("Closing PrintWriter");
            out.close();
        } 
        else {
            System.out.println("PrintWriter not open");
        }
    }
}

如何抛出异常?

  1. 方法体内通过 throw 关键字抛出异常

  2. 方法签名处通过 throws 关键字抛出异常

public void writeList() throws IOException 

异常类继承体系
image

异常类命名规则
命名以Exception结尾.

I/O

I/O 流

I/O流表示输入源或输出目标。一个流可以表示许多不同类型的源和目的地,包括磁盘文件、设备、其他程序和内存数组。

image
image

Byte Streams字节流
程序使用字节流演示字节的输入和输出.
所有字节流都是InputStreamOutputStream的后代.

FileInputStream文件字节流作为示例:

package com.black.basic.io.bytestreams;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * 文件字节流演示类
 * 
 * @author black
 * @date 2022-01-19 10:21:032
 */
public class FileIOStreamDemo {

	public static void main(String[] args) {

		FileIOStreamDemo fileIOStream = new FileIOStreamDemo();

		// 获取工程里文件
		ClassLoader classLoader = fileIOStream.getClass().getClassLoader();
		String file = classLoader.getResource("com/black/basic/io/test_file_en.txt").getFile();
		// 打印文件内容
		fileIOStream.printContent(file);
		System.out.println("==========新增内容前===========");
		// 追加一行内容
		String pinkAge = "Pink 28\n";
		fileIOStream.writeFile(file, pinkAge, true);
		// 打印文件内容
		System.out.println("==========新增内容后===========");
		fileIOStream.printContent(file);
	}

	// 读取文件打印内容
	public void printContent(String file) {
		String content;
		try {
			content = this.readFile(file);
			System.out.println("文件里的内容:");
			System.out.print(content);
		} catch (FileNotFoundException e) {
			System.out.println(file + "文件不存在!");
		} catch (IOException e) {
			System.out.println("读取文件异常!,异常信息:" + e.getMessage());
		}
	}

	// 读取文件
	public String readFile(String fileName) throws FileNotFoundException, IOException {
		StringBuilder content = new StringBuilder();
		File file = new File(fileName);
		// 如果文件不存在或不能写入,则不进行读取
		if (!file.exists()) {
			throw new FileNotFoundException(file + "文件不存在!");
		}
		if (!file.canRead()) {
			throw new IOException(file + "文件无读取权限!");
		}

		// 从 InputStream 读取输入进来的字节
		try (FileInputStream in = new FileInputStream(file)) {
			// 字节
			int b;
			while ((b = in.read()) > 0) {
				content.append((char) b);
			}
		}
		// jdk 1.7 开始当 try 块退出时,会释放资源,此处不用再在finally中调用 in.close()关闭流释放资源
		// 返回读取到的内容
		return content.toString();
	}

	// 写入文件
	public void writeFile(String fileName, String content, boolean append) {
		File file = new File(fileName);
		// 如果文件不存在或不能写入,则不进行读取
		if (!file.exists()) {
			System.out.println(file + "文件不存在!");
			return;
		}
		if (!file.canWrite()) {
			System.out.println(file + "文件无法写入!");
			return;
		}

		try (FileOutputStream out = new FileOutputStream(file, append)) {
			out.write(content.getBytes());
			out.flush();
		} catch (FileNotFoundException e) {
			System.out.println(file + "文件不存在!");
		} catch (IOException e) {
			System.out.println("写入文件异常!,异常信息:" + e.getMessage());
		}
	}
}

test_file_en.txt 在 com.black.basic.io包中, 文件内容:

# test file encoding UTF-8
name  age


测试结果:

文件里的内容:
# test file encoding UTF-8
name  age

==========新增内容前===========
==========新增内容后===========
文件里的内容:
# test file encoding UTF-8
name  age

Pink 28

注意: 每次使用完流后必须关闭流,最好是在finally块中关闭.

Character Streams字符流
Java 平台使用 Unicode 规范存储字符.
所有字符流都是 ReaderWriter类的后代.
这里使用 FileReaderFileWriter作为示例:

package com.black.basic.io.bytestreams;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

/**
 * 文件字符流演示类
 * 
 * @author black
 * @date 2022-01-19 10:21:032
 */
public class FileIOCharacterStreamDemo {

	public static void main(String[] args) {

		FileIOCharacterStreamDemo fileIOStream = new FileIOCharacterStreamDemo();

		// 获取工程里文件
		ClassLoader classLoader = fileIOStream.getClass().getClassLoader();
		String file = classLoader.getResource("com/black/basic/io/test_file_en.txt").getFile();
		// 打印文件内容
		fileIOStream.printContent(file);
		System.out.println("==========新增内容前===========");
		// 追加一行内容
		String pinkAge = "Pink 28\n";
		fileIOStream.writeFile(file, pinkAge, true);
		// 打印文件内容
		System.out.println("==========新增内容后===========");
		fileIOStream.printContent(file);
	}

	// 读取文件打印内容
	public void printContent(String file) {
		String content;
		try {
			content = this.readFile(file);
			System.out.println("文件里的内容:");
			System.out.print(content);
		} catch (FileNotFoundException e) {
			System.out.println(file + "文件不存在!");
		} catch (IOException e) {
			System.out.println("读取文件异常!,异常信息:" + e.getMessage());
		}
	}

	// 读取文件
	public String readFile(String fileName) throws FileNotFoundException, IOException {
		StringBuilder content = new StringBuilder();
		File file = new File(fileName);
		// 如果文件不存在或不能写入,则不进行读取
		if (!file.exists()) {
			throw new FileNotFoundException(file + "文件不存在!");
		}
		if (!file.canRead()) {
			throw new IOException(file + "文件无读取权限!");
		}

		// 从 FileReader 读取字符,通过父类InputStreamReader与字节流转换
		try (FileReader reader = new FileReader(file)) {
			// 字符
			int c;
			// 每次读取一个字符
			while ((c = reader.read()) > 0) {
				content.append((char) c);
			}
		}
		// jdk 1.7 开始当 try 块退出时,会释放资源,此处不用再在finally中调用 in.close()关闭流释放资源
		// 返回读取到的内容
		return content.toString();
	}

	// 写入文件
	public void writeFile(String fileName, String content, boolean append) {
		File file = new File(fileName);
		// 如果文件不存在或不能写入,则不进行读取
		if (!file.exists()) {
			System.out.println(file + "文件不存在!");
			return;
		}
		if (!file.canWrite()) {
			System.out.println(file + "文件无法写入!");
			return;
		}

		try (FileWriter writer = new FileWriter(file, append)) {
			writer.write(content);
			writer.flush();
		} catch (FileNotFoundException e) {
			System.out.println(file + "文件不存在!");
		} catch (IOException e) {
			System.out.println("写入文件异常!,异常信息:" + e.getMessage());
		}
	}
}

面向行操作的流:BufferedReader ,BufferedWriter :

package com.black.basic.io.bytestreams;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

/**
 * BufferedReader 示例
 * @author black
 * @date 2022-01-19 11:41:023
 */
public class FileIOBufferedReadDemo {


	public static void main(String[] args) {

		FileIOBufferedReadDemo fileIOStream = new FileIOBufferedReadDemo();

		// 获取工程里文件
		ClassLoader classLoader = fileIOStream.getClass().getClassLoader();
		String file = classLoader.getResource("com/black/basic/io/test_file_en.txt").getFile();
		// 打印文件内容
		fileIOStream.printContent(file);
		System.out.println("==========新增内容前===========");
		// 追加一行内容
		String pinkAge = "Pink 28\n";
		fileIOStream.writeFile(file, pinkAge, true);
		// 打印文件内容
		System.out.println("==========新增内容后===========");
		fileIOStream.printContent(file);
	}

	// 读取文件打印内容
	public void printContent(String file) {
		String content;
		try {
			content = this.readFile(file);
			System.out.println("文件里的内容:");
			System.out.print(content);
		} catch (FileNotFoundException e) {
			System.out.println(file + "文件不存在!");
		} catch (IOException e) {
			System.out.println("读取文件异常!,异常信息:" + e.getMessage());
		}
	}

	// 读取文件
	public String readFile(String fileName) throws FileNotFoundException, IOException {
		StringBuilder content = new StringBuilder();
		File file = new File(fileName);
		// 如果文件不存在或不能写入,则不进行读取
		if (!file.exists()) {
			throw new FileNotFoundException(file + "文件不存在!");
		}
		if (!file.canRead()) {
			throw new IOException(file + "文件无读取权限!");
		}

		// 从 BufferedReader 读取字符
		try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
			// 行
			String line;
			// 每次读取一行
			while ((line = reader.readLine()) != null) {
				content.append(line);
				// readline会去除回车换行,所以这里手动给拼接上
				content.append("\n");
			}
		}
		// jdk 1.7 开始当 try 块退出时,会释放资源,此处不用再在finally中调用 in.close()关闭流释放资源
		// 返回读取到的内容
		return content.toString();
	}

	// 写入文件
	public void writeFile(String fileName, String content, boolean append) {
		File file = new File(fileName);
		// 如果文件不存在或不能写入,则不进行读取
		if (!file.exists()) {
			System.out.println(file + "文件不存在!");
			return;
		}
		if (!file.canWrite()) {
			System.out.println(file + "文件无法写入!");
			return;
		}

		try (BufferedWriter writer = new BufferedWriter(new FileWriter(file, append))) {
			writer.write(content);
			writer.flush();
		} catch (FileNotFoundException e) {
			System.out.println(file + "文件不存在!");
		} catch (IOException e) {
			System.out.println("写入文件异常!,异常信息:" + e.getMessage());
		}
	}

}

Scanner 扫描
Scanner 可以扫描输入

从命令行读取IO
两种方式:

  • 标准流(操作系统的一个特性,Java 中标准流相关类:System.in ,System.out,System.err)
  • Console(System.console())

Console 使用参见下面代码:

import java.io.Console;
import java.util.Arrays;
import java.io.IOException;

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

        Console c = System.console();
        if (c == null) {
			// 在Eclipse 运行,一定会是null,使用CMD窗口运行则不会返回null
            System.err.println("No console.");
            System.exit(1);
        }

        String login = c.readLine("Enter your login: ");
        char [] oldPassword = c.readPassword("Enter your old password: ");

        if (verify(login, oldPassword)) {
            boolean noMatch;
            do {
                char [] newPassword1 = c.readPassword("Enter your new password: ");
                char [] newPassword2 = c.readPassword("Enter new password again: ");
                noMatch = ! Arrays.equals(newPassword1, newPassword2);
                if (noMatch) {
                    c.format("Passwords don't match. Try again.%n");
                } else {
                    change(login, newPassword1);
                    c.format("Password for %s changed.%n", login);
                }
                Arrays.fill(newPassword1, ' ');
                Arrays.fill(newPassword2, ' ');
            } while (noMatch);
        }

        Arrays.fill(oldPassword, ' ');
    }
    
    // Dummy change method.
    static boolean verify(String login, char[] password) {
        // This method always returns
        // true in this example.
        // Modify this method to verify
        // password according to your rules.
        return true;
    }

    // Dummy change method.
    static void change(String login, char[] password) {
        // Modify this method to change
        // password according to your rules.
    }
}

Data streams数据流
数据流支持基本类型和String类型的二进制IO.所有数据流要么实现了 DataInput接口要么实现 DataOutput接口.

in = new DataInputStream(new
            BufferedInputStream(new FileInputStream(dataFile)));

double price;
int unit;
String desc;
double total = 0.0;

try {
    while (true) {
        price = in.readDouble();
        unit = in.readInt();
        desc = in.readUTF();
        System.out.format("You ordered %d" + " units of %s at $%.2f%n",
            unit, desc, price);
        total += unit * price;
    }
} catch (EOFException e) {
	// 内容读取结束
}

数据流通过捕获 EOFException 异常判断文件内容读取完毕.

Object Streams对象流
实现了Serializable接口的对象,可进行序列化.对象流的类: ObjectInputStream 和 ObjectOutputStream.
多个被引用对象的I/O,如下图:
image

文件 I/O

java.nio.file 包和 java.nio.file.attr 包提供了对文件IO的支持. Path类是学习文件 IO的入口.

什么是 Path?

Path 就是 用/拼接的目录.

目录结构图:
image

符号连接:
image
从 Java SE 7 开始引入 Path 类, Path 是 java.nio.file 包的主要入口之一.

创建一个 路径:

Path p1 = Paths.get("/tmp/foo");
Path p2 = Paths.get(args[0]);
Path p3 = Paths.get(URI.create("file:///Users/joe/FileTest.java"));

检索路径下的信息:

// Solaris syntax
Path path = Paths.get("/home/joe/foo");

System.out.format("toString: %s%n", path.toString());
System.out.format("getFileName: %s%n", path.getFileName());
System.out.format("getName(0): %s%n", path.getName(0));
System.out.format("getNameCount: %d%n", path.getNameCount());
System.out.format("subpath(0,2): %s%n", path.subpath(0,2));
System.out.format("getParent: %s%n", path.getParent());
System.out.format("getRoot: %s%n", path.getRoot());

image

结合两个路径:

Path p1 = Paths.get("/home/joe/foo");
// Result is /home/joe/foo/bar
System.out.format("%s%n", p1.resolve("bar"));

// Result is /home/joe
Paths.get("foo").resolve("/home/joe");

获取两个路径的相对路径:

Path p1 = Paths.get("home");
Path p3 = Paths.get("home/sally/bar");
// Result is sally/bar
Path p1_to_p3 = p1.relativize(p3);  // p1 打开  sally/bar 可切换到 p3路径
// Result is ../..
Path p3_to_p1 = p3.relativize(p1);// p3 打开  ../.. 可切换到 p1路径

比较两个路径:

Path path = ...;
Path otherPath = ...;
Path beginning = Paths.get("/home");
Path ending = Paths.get("foo");

if (path.equals(otherPath)) {
    // equality logic here
} else if (path.startsWith(beginning)) {
    // path begins with "/home"
} else if (path.endsWith(ending)) {
    // path ends with "foo"
}

java.io.Filejava.nio.file API对应关系:
image
image

并发

进程和线程

并发编程中,2个基本执行单元:进程和线程
进程
进程拥有自己的运行环境,一个进程通常有一个完整的、私有的基本运行时资源集;特别是,每个进程都有自己的内存空间。

一个应用可能是由多个进程组成,进程之间需要通过 Inter Process Communication (IPC) 资源通信,比如 socket ,pipe等.

大多数 JVM 的实现运行在一个单进程中.Java 可以通过 ProcessBuilder 对象创建新的进程.
线程
线程有时被称为轻量级进程,进程和线程都提供一个执行环境,但是线程花费的资源更小.
每个进程最少有1个线程.线程共享进程的资源,比如进程的内存和打开的文件.
Java 平台多线程执行是个至关重要的特性.
启动应用程序的线程被称为主线程,负责创建额外的其他线程.

Thread
每个线程都与Thread类的实例相关联.

定义并启动一个线程
定义线程:1. 实例化 Thread 类 2. 实例化 Thread类的子类(后代)
启动线程: 调用 start 方法

new Thread().start();

使用线程
一个 Thread 对象对应一个线程,当调用 start() 方法就会启动一个新的线程,去执行这个Thread对象的run()方法.

注意: 只有调用 start() 方法才会生成1个新的线程.

Thread 对象的run方法是来自于 Runable接口.

run 方法源码:

public
class Thread implements Runnable {
	...
  public Thread(Runnable target, String name) {
        init(null, target, name, 0);
    }
    ...
	@Override
    public void run() {
		//  target 是 Runnable类型,
        if (target != null) {
            target.run();
        }
    }
    ...
}

从以上代码可以看出, 启动一个线程后,线程必须去执行Runable接口的run()方法. Thread 类本身也是Runable.
当我们在使用线程时,其实就是在 run 方法里添加代码.

使用线程的几种方式:

  • 实现 Runable 接口编写run方法; 将此对象通过传给 Thread 对象或者传给线程池执行.
public class HelloRunnable implements Runnable {

    public void run() {
        System.out.println("Hello from a thread!");
    }

    public static void main(String args[]) {
        (new Thread(new HelloRunnable())).start();
    }

}
  • 继承 Thread 类,重写 Thread类的run方法.
public class HelloThread extends Thread {

    public void run() {
        System.out.println("Hello from a thread!");
    }

    public static void main(String args[]) {
        (new HelloThread()).start();
    }

}

  • 实现 Callable 接口,编写call() 方法,将此对象传给线程池执行.
package com.black.basic.io.bytestreams;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * callable 接口示例 
 * @author lihw
 * @date 2022-01-24 10:33:018
 */
public class CallableTaskDemo implements Callable<String> {

	@Override
	public String call() throws Exception {
		// 重写 call 方法,添加任务处理逻辑,处理完成,返回成功
		System.out.println("\tcallable task runing ... ");
		TimeUnit.SECONDS.sleep(3);
		return "\tsuccess";
	}

	public static void main(String[] args) throws InterruptedException, ExecutionException {
		// 声明一个有2个线程的线程池
		ExecutorService executor = Executors.newFixedThreadPool(2);
		// 声明并实例化一个 callable 任务
		CallableTaskDemo task = new CallableTaskDemo();
		// 将任务提交给线程池执行
		System.out.println("开始执行任务...");
		Future<String> result = executor.submit(task);
		// 同步等待任务执行后结果
		System.out.println("\ttask result : " + result.get());
		// 关闭线程池
		System.out.println("开始关闭线程池... ");
		executor.shutdown();
		// 循环等待线程池关闭
		while (true) {
			if (executor.isTerminated()) {
				System.out.println("线程池已关闭 ");
				break;
			}
		}
	}
}

sleep 休眠
使当前线程休眠,休眠状态可被中断.

// Thread.sleep休眠3秒
Thread.sleep(1000L);
// 使用 TimeUnit 休眠3秒
TimeUnit.SECONDS.sleep(3);

中断
中断就是告诉一个线程应该停止的指令.线程具体如何响应中断是由程序员决定的.
问:如何中断一个线程?
答: 调用 t.interrupt() 方法中断线程t.
问:如何探测到线程被中断了?
答:
1. Thread.currentThread().isInterrupted() 方法可判断线程是否被中断(不清除中断标志)
2. Thread.currentThread().interrupted() 方法可判断线程是否被中断过(清除中断标志,该方法调用后,isInterrupted() 返回false)
3. 通过捕获 InterruptedException 异常(比如 sleep方法,join 方法都会抛出此异常 )

测试代码:

package com.black.basic.io.bytestreams;

import java.util.concurrent.TimeUnit;

/**
 * 中断示例
 * 
 * @author lihw
 * @date 2022-01-24 10:57:035
 */
public class InterruptDemo {

	public static void main(String[] args) {

		// 测试 Thread.currentThread().isInterrupted()
		isInterrupted();
		// 测试 Thread.currentThread().interrupted()
//		interrupted();
	}

	public static void isInterrupted() {
		// 定义任务
		Thread t1 = new Thread(() -> {
			while (true) {
				System.out.println(Thread.currentThread().getName() + "任务执行中...");
				// 判断执行此任务的线程是否被中断(isInterrupted() 不会清除中断标志)
				if (Thread.currentThread().isInterrupted()) {
					System.out.println("任务被中断");
					System.out.println("当前线程" + Thread.currentThread().getName() + "中断状态:"
							+ Thread.currentThread().isInterrupted());
					// 探测到该线程被中断过,那么需要对中断行为进行一个响应,我这里是终止循环;如果这里终止循环,那么代码仍会继续执行
					break;
				}
			}
		}, "t1");

		// 开启一个新线程执行任务
		t1.start();
		try {
			// 主线程等待 1 毫秒
			TimeUnit.MICROSECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		// 主线程中断 t 线程
		t1.interrupt();
	}

	// 测试 
	public static void interrupted() {
		// 定义任务
		Thread t2 = new Thread(() -> {
			while (true) {
				System.out.println(Thread.currentThread().getName() + "任务执行中...");
				// 判断执行此任务的线程是否被中断过(interrupted() 会清除被中断标志;)
				if (Thread.currentThread().interrupted()) {
					System.out.println("任务被中断");
					System.out.println("当前线程" + Thread.currentThread().getName() + "中断状态:"
							+ Thread.currentThread().isInterrupted());
					// 探测到该线程被中断过,那么需要对中断行为进行一个响应,我这里是终止循环;如果这里终止循环,那么代码仍会继续执行
					break;
				}
			}
		}, "t2");

		// 开启一个新线程执行任务
		t2.start();
		try {
			// 主线程等待 1 毫秒
			TimeUnit.MICROSECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		// 主线程中断 t 线程
		t2.interrupt();
	}
}

join 方法
join 方法允许一个线程等待另一个线程执行完成.

// t 是一个已经在运行的线程对象,当前线程会等待 t 线程执行结束再继续执行.
t.join();

join 方法可以通过抛出 InterruptedException 异常来响应中断.

平台环境

配置实用工具
配置实用程序描述了用于访问部署应用程序时或应用程序用户提供的配置数据的api。

  1. Properties
    Properties 是键值对管理的配置值,java 中使用 java.util.Properties 管理属性.

属性在应用中的生命周期:
image

. . .
// create and load default properties
Properties defaultProps = new Properties();
FileInputStream in = new FileInputStream("defaultProperties");
defaultProps.load(in);
in.close();

// create application properties with default
Properties applicationProps = new Properties(defaultProps);

// now load properties 
// from last invocation
in = new FileInputStream("appProperties");
applicationProps.load(in);
in.close();
. . .
  1. 命令行参数
    Java 程序启动类的 main 方法能够接收命令行参数:
public class EchoTest {
    public static void main (String[] args) {
        for (String s: args) {
            System.out.println(s);
        }
    }
}

运行 EchoTest


java EchoTest Drink Hot Java
// 控制台输出
Drink
Hot
Java
  1. 环境变量
    Java 通过 System.getenv方法检索可用的环境变量.
import java.util.Map;

public class EnvMap {
    public static void main (String[] args) {
        Map<String, String> env = System.getenv();
        for (String envName : env.keySet()) {
            System.out.format("%s=%s%n",
                              envName,
                              env.get(envName));
        }
    }
}

将环境变量传递给新的进程:

// 使用 ProcessBuilder对象创建新的进程,默认使用Java虚拟机的环境变量,可通过  ProcessBuilder.environment 方法修改新进程的环境变量

  1. 其他配置工具
  • Preferences API 保存用户偏好信息
    同一个程序在每次运行完后,可以通过Preferences来记录用户的偏好(参考博客:https://www.cnblogs.com/zhongshiqiang/p/5852125.html)

  • Jar 包中使用 manifest 描述压缩包里的内容

  • Java Web Start应用的配置包含在jnlp文件中.

  • Java插件applet的配置部分取决于将applet嵌入到web页面中的HTML标记。

  • java.util.ServiceLoader提供了SPI扩展机制

系统工具

System类提供了一些预定义的 IO对象,

一些重要的系统属性:
image

读取系统属性:

System.getProperty("path.separator");

设置系统属性:

FileInputStream propFile =
            new FileInputStream( "myProperties.txt");
        Properties p =
            new Properties(System.getProperties());
        p.load(propFile);
System.setProperties(p);

系统安全
SecurityManager是一个定义了应用程序的安全策略的对象.
获取 :

// 如果没有安全策略,则返回 null
SecurityManager appsm = System.getSecurityManager();

正则表达式

集合

日期时间API

posted on 2021-12-31 12:29  不安分的黑娃  阅读(85)  评论(0编辑  收藏  举报