java 泛型, 多线程, 集合, 网络编程, 流
设置JAVA_HOME
一是为了方便引用,比如,JDK安装在C:\jdk1.6.0目录里,则设置JAVA_HOME为该目录路径, 那么以后要使用这个路径的时候, 只需输入%JAVA_HOME%即可, 避免每次引用都输入很长的路径串;
二则是归一原则, 当JDK路径改变的时候, 仅需更改JAVA_HOME的变量值即可, 否则,就要更改任何用绝对路径引用JDK目录的文档, 要是万一没有改全, 某个程序找不到JDK, 后果是可想而知的----系统崩溃!
三则是第三方软件会引用约定好的 JAVA_HOME 变量, 不然, 你不能正常使用该软件。
在系统环境变量那一栏中点 -> 新建 JAVA_HOME (JAVA_HOME指向的是JDK的安装路径)
path 变量
path 变量使得我们能够在系统中的任何地方运行java应用程序,比如 javac、java、javah 等等,这就要找到我们安装 JDK 的目录,
假设我们的JDK安装在 C:\jdk1.6.0 目录下,那么在 C:\jdk1.6.0\bin 目录下就是我们常用的 java 应用程序,我们就需要把 C:\jdk1.6.0\bin 这个目录加到 path 环境变量里面。4
classpath 变量
classpath 环境变量,是当我们在开发java程序时需要引用别人写好的类时,要让 java 解释器知道到哪里去找这个类。通常,sun 为我们提供了一些额外的丰富的类包,一个是 dt.jar,一个是 tools.jar,这两个 jar 包都位于 C:\jdk1.6.0\lib
目录下,所以通常我们都会把这两个 jar 包加到我们的 classpath
环境变量中 set classpath=.;C:\jdk1.6.0\lib\tools.jar;C:\jdk1.6.0\lib\dt.jar
。
注意在完成配置环境变量后测试JDK是否安装成功时键入命令:java -version
嵌在一个类中声明的类叫做内部类,有静态内部类和非静态内部类两种。 内部类可以访问外部类的属性和方法,这一特点非常好!
非静态内部类 - 内部类有默认,private
和protected
修饰符
静态内部类 - 静态内部类生成对象时,比非静态类方便些
class OuterClass {
public static void main(String[] args) {
// 使用非静态内部类 - 先生成外部类对象,然后根据外部类对象生成内部类对象
OuterClass myouter = new OuterClass();
OuterClass.InnerClass myinner = myouter.new InnerClass();
myinner.info();
// 使用静态内部类 - 直接使用类名即可
OuterClass.StaticInnerClass mySinner = new OuterClass.StaticInnerClass();
System.out.println(mySinner.xx);
OuterClass.StaticInnerClass.info();
}
private int y = 3;
static private int m = 4;
private void say() { System.out.println("hello"); }
// 非静态内部类
class InnerClass {
public void info() {
// 内部类可以访问外部类的属性-私有的也行
System.out.println(y);
say();
}
}
// 静态内部类
static class StaticInnerClass {
int xx = 5;
static public void info() {
System.out.println(m);
}
}
}
在内部类基础上,如果类名没有给定,那么这就是一个匿名类。
匿名类主要用于在需要的时候创建一个对象执行特定任务,使得代码相比于内部类简洁.
匿名类通常继承一个父类或实现一个接口
interface Polygon {
void display();
}
class Father {
public void display() {
System.out.println("world");
};
}
class OuterClass {
// 匿名类 - 实现接口 - 实现了接口的方法
public Polygon p1 = new Polygon(){
public void display() {
System.out.print("p1 hello");
}
};
// 匿名类 - 继承父类 - 并重写了父类的方法
public Father p2 = new Father() {
public void display() {
System.out.print("p2 hello");
}
};
public static void main(String[] args) {
OuterClass outer = new OuterClass();
outer.p1.display();
outer.p2.display();
}
}
基础类型自动转换
低 ------------------------------------> 高
byte,short,char—> int —> long—> float —> double
数据类型转换必须满足如下规则:
boolean
类型进行类型转换。
int i =128;
byte b = (byte)i;
因为 byte 类型是 8 位,最大值为127,所以当 int
强制转换为 byte
类型时,值 128 时候就会导致溢出。
(int)23.7 == 23;
(int)-45.89f == -45
修饰符 | 当前类 | 同一包内 | 子孙类(同一包) | 子孙类(不同包) | 其他包 |
---|---|---|---|---|---|
| Y | Y | Y | Y | Y |
| Y | Y | Y | Y/N(说明) | N |
| Y | Y | Y | N | N |
| Y | N | N | N | N |
修饰符的继承规则 - 一句话: 子类可以放大父类的权限,但是不能缩小!
非访问修饰符
为了实现一些其他的功能,Java 也提供了许多非访问修饰符。
当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。
StringBuilder 类在和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问),由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
数组声明 dataType[] arrayRefVar
数组创建 arrayRefVar = new dataType[arraySize];
可以把两个合起来,如dataType[] arrayRefVar = new dataType[arraySize]
或字面量方式dataType[] arrayRefVar = {value0, value1, ...}
处理数组 用for
循环或者for-each
循环,后者无法在循环体内修改数组元素的值。 for-each
的语法for( type element : array) {}
多维数组:真正的说,多维数组其实是一维数组,只是它的数组元素仍旧是数组元素 - 见下面的例子
dataType[][][] typeName = new dataType[length1][length2][length3]
length1表示第一维数组的数组长度(元素个数), length2为第一维数组元素指向的数组的数组长度, 依次类推!
String[] s1Arr = {"hello", "world"};
String[] s2Arr = {"ok", "good"};
// sArr1是一个一维数组,它的数组元素类型是String[2](2个元素的String[])
String[][] sArr1 = { {"hello", "world"}, {"ok", "good"}};
String[][] sArr2 = {s1Arr, s2Arr};
System.out.println(sArr1[0][0]);
System.out.println(sArr2[0][0]);
java.util.Arrays
类能方便地操作数组,它提供的所有方法都是静态的。具有以下功能:
fill
方法。sort
方法,按升序。equals
方法比较数组中元素值是否相等。binarySearch
方法能对排序好的数组进行二分查找法操作。java.util
包提供了 Date 类来封装当前的日期和时间。 Date
类提供两个构造函数来实例化Date
对象。
Date now = new Date(); // 构造方法1
Date someDay = new Date(1600560136669L); // 构造方法2 - 注意输入的是long类型
System.out.println(now.getTime()); // 返回距离1970.01.01 00:00:00的毫秒数
System.out.println(someDay.toString()); // Sun Sep 20 08:02:16 CST 2020
// 返回String,格式为dow mon dd hh:mm:ss zzz yyyy 其中: dow 是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat)
日期对象的比较 通过Date
对象提供的3个方法可以比较日期对象的大小
getTime()
方法获取两个日期(自1970年1月1日经历的毫秒数值),然后比较这两个值。before()
,after()
和 equals()
compareTo()
方法,它是由 Comparable 接口定义的,Date 类实现了这个接口。日期格式化输出 利用SimpleDateFormat
类可以自定义日期时间输出,比Date
类的输出更加友好。 还能利用printf
格式输出
Date now = new Date();
SimpleDateFormat ft = new SimpleDateFormat("yyyy-MM-dd FF D E HH:mm:ss zzz"); // 入参为输出格式
System.out.println(ft.format(now)); // 2021-09-02 1 245 Thu 14:36:57 CST - F是从0开始的,0代表周日
// 使用两个字母格式,它以 %t 开头并且以下面表格中的一个字母结尾 - c, F, D, r, T, R// c的使用 - Thu Sep 02 14:41:24 CST 2021System.out.printf("全部日期和时间信息:%tc%n", now);// f的使用 - 2021-09-02System.out.printf("年-月-日格式:%tF%n", now);// d的使用 - 09/02/21System.out.printf("月/日/年格式:%tD%n", now);// r的使用 - 02:41:24 PMSystem.out.printf("HH:MM:SS PM格式(12时制):%tr%n", now);// t的使用 - 14:41:24System.out.printf("HH:MM:SS格式(24时制):%tT%n", now);// R的使用 - 14:41System.out.printf("HH:MM格式(24时制):%tR", now);
SimpleDateFormat
**格式表**
字母 | 描述 | 示例 |
---|---|---|
G | 纪元标记 | AD |
y | 四位年份 | 2001 |
M | 月份 | July or 07 |
d | 一个月的日期 | 10 |
h | A.M./P.M. (1~12)格式小时 | 12 |
H | 一天中的小时 (0~23) | 22 |
m | 分钟数 | 30 |
s | 秒数 | 55 |
S | 毫秒数 | 234 |
E | 星期几 | Tuesday |
D | 一年中的日子 | 360 |
F | 一个月中第几周的周几 | 2 (周日=0, 周一=1..) |
w | 一年中第几周 | 40 |
W | 一个月中第几周 | 1 |
a | A.M./P.M. 标记 | PM |
k | 一天中的小时(1~24) | 24 |
K | A.M./P.M. (0~11)格式小时 | 10 |
z | 时区 | Eastern Standard Time |
' | 文字定界符 | Delimiter |
" | 单引号 | ` | | |
可以看出使用Date
类的使用有诸多不便, 比如无法获取日期时间数据中的一部分,如小时, 日,分钟。也不能对上述部分数据做计算,因此Calendar
类就是为了解决这些问题。
Calendar c = Calendar.getInstance();//默认是当前日期//创建一个代表2021年9月12日的Calendar对象Calendar c1 = Calendar.getInstance();c1.set(2021, 9, 12); c1.set(Calendar.DATE, 10); // 设置时间中的部分c1.add(Calendar.DATE, -10); // 对时间中的部分做计算// 获得月份int month = c1.get(Calendar.MONTH) + 1; // 月份从0开始!
GregorianCalendar类是Calendar
类的一个具体实现!
Java中正则表示通过包java.util.regex
下的Pattern
类和Matcher
类实现。 前者表示正则表达式类,后者表示匹配类
匹配类中有三大种类方法: 索引方法, 查找方法, 替换方法
String content = "helloworld"; String patten = ".*llo.*"; // 快速用法 - 类型JS中正则表达式对象的test()方法 /.*llo.*/.test() boolean isMatch = Pattern.matches(patten, content); // 获取捕获结果 - 类似JS正则表示的exec()方法 String line = "This order was placed for QT3000! OK?"; String patten2 = "(\\D*)(\\d+)(.*)"; Pattern pattern = Pattern.compile(patten2); Matcher match = pattern.matcher(line); if (match.find()) { // 第一个捕获组永远是全匹配的表达式 System.out.println("第一个捕获组" + match.group(0)); System.out.println("第一个捕获组" + match.group(2)); // 3000 }
该包下面还有一个异常类,用于捕获正则表达式的错误
Java.io
包几乎包含了所有操作输入、输出需要的类。所有这些流类代表了输入源和输出目标。
Java.io
包中的流支持很多种格式,比如:基本类型、对象、本地化字符集等等。
控制台输入流
Java的控制台输入由System.in
完成,可以把System.in
包装在一个BufferReader
对象里来创建一个字符流
该类的read()
从输入流获取一个字符,并把该字符作为整数返回,当流结束时候返回-1。
该类的readLine()
方法从输入流中获取一个字符串
BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));char c ;String str;try{ do{ c = (char)(bf.read()); System.out.print(c); }while(c != 'Q'); do { str = bf.readLine(); System.out.println(str); }while(!str.equals("end"));}catch(IOException e) { System.out.print(e.getMessage());}
文件流
一个流被定义为一个数据序列。输入流用于从源读取数据,输出流用于向目标写数据。
下面是输入流和输出流的类层次图
<img src="https://www.runoob.com/wp-content/uploads/2013/12/iostream2xx.png" alt="img" style="zoom:80%;" />
// 读取,存取文件主要是两个类FileInputStream 和 FileOutputStreamString path = "C:\\Users\\I334870\\Desktop\\hello.txt";byte bWrite[] = {11, 21, 3, 40, 5};try{ OutputStream ops = new FileOutputStream(path); for(byte b : bWrite) { ops.write(b); } ops.close(); InputStream ips = new FileInputStream(path); int size = ips.available(); for(int i=0; i<size; i++) { System.out.print((char)ips.read() + "|"); } ips.close();}catch(IOException e) { System.out.println(e.getMessage());}
上面用的是字节流输出和输入,可能导致控制台的输出是乱码,现在以字符流输出,以避免乱码
String path = "C:\\Users\\I334870\\Desktop\\hello.txt";try{ // 写入! File file = new File(path); FileOutputStream fops = new FileOutputStream(file); OutputStreamWriter writer = new OutputStreamWriter(fops, "UTF-8"); writer.append("hello"); // 写入缓冲区 writer.append("\n"); writer.append("world"); writer.close(); fops.close(); // 读取 FileInputStream fip = new FileInputStream(file); InputStreamReader reader = new InputStreamReader(fip, "UTF-8"); StringBuffer sb = new StringBuffer(); while(reader.ready()) { sb.append((char)reader.read()); } System.out.println(sb.toString()); reader.close(); fip.close();}catch(IOException e) { System.out.println(e.getMessage());}
文件目录
一些关于文件和I/O的类
File
类 该类由两个方法创建文件夹: mkdir()
, mkdirs()
。第二个方法会根据路径依次创建文件夹及其父文件夹
FileReader
类FileWriter
类除了上述的BufferedReader
类, 还有java.util.Scanner
类获取输入流
Scanner scanner = new Scanner(System.in);System.out.println("start input");while (scanner.hasNext()) { String str1 = scanner.next(); System.out.print(str1);}while(scanner.hasNextLine() ) { String str1 = scanner.nextLine(); System.out.print(str1);}scanner.close();
next() 与 nextLine() 区别
next()
:
next()
方法会自动将其去掉。next()
不能得到带有空格的字符串。nextLine()
:
如果要输入 int
或float
类型的数据,在 Scanner
类中也有支持,但是在输入之前最好先使用hasNextXxx()
方法进行验证,再使用 nextXxx()
来读取
Java 枚举是一个特殊的类,一般表示一组常量(常量就是枚举类的实例!),使用 enum 关键字来定义,各个常量使用逗号 , 来分割。
class OuterClass { public static void main(String[] args) { Color red = Color.RED; for (Color col : Color.values()) { switch (col) { case RED: System.out.println(Color.RED); break; case BLUE: System.out.println(Color.BLUE); break; case GREEN: System.out.println(Color.GREEN); break; } } } // 枚举类也可以作为内部类存在 enum Color { RED, GREEN, BLUE; }}
如果一个类的包名如下,那么这个类路径是"p1/p2/p3/myclass.java"
package p1.p2.p3;class myclass {}
编译时候,在java文件的目录下,不需要指定包名,但要指定class文件的输出地址javac -d . myclass.java
生成的字节码
class
文件会被放到对应的路径下面"p1/p2/p3/"
执行时候,在Java文件的目录下,要指定包名, java p1.p2.p3.myclass
为了在执行
java
命令时候方便,可以将包路径写入到CLASSPATH
中
任何对象加入集合类后,自动转变为Object类型,所以在取出的时候,需要进行强制类型转换。
集合框架一览图
<img src="https://www.runoob.com/wp-content/uploads/2014/01/2243690-9cd9c896e0d512ed.gif" alt="img" style="zoom:80%;" />
集合框架分层图
<img src="https://www.runoob.com/wp-content/uploads/2014/01/java-coll-2020-11-16.png" alt="img" style="zoom: 80%;" />
ArrayList 类是一个可以动态修改的数组,提供了相关的添加、删除、修改、遍历等功能。
它支持按照索引来访问元素!
<img src="https://www.runoob.com/wp-content/uploads/2020/06/ArrayList-1-768x406-1.png" alt="img" style="zoom:80%;" />
使用该集合类的语法是 该类提供另外的方法,具体可以见帮助文档
import java.util.ArrayList;ArrayList<E> objectName = new ArrayList<>();ArrayList<Integer> cities = new ArrayList<>();// 添加元素cities.add(1);cities.add(2);cities.add(3);cities.add(5);// 修改元素cities.set(2, 3);// 移除元素cities.remove(3);// 访问元素System.out.print(cities.get(2));// 排序Collections.sort(cities);
链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的地址。链表可分为单向链表和双向链表。
它不能按照索引来访问元素!与 ArrayList 相比,LinkedList 的增加和删除对操作效率更高,而查找和修改的操作效率较低。
LinkedList
是一个多面手啊, 实现了真他妈多的接口!
LinkedList
继承了 AbstractSequentialList
类。
LinkedList
实现了 Queue
接口,可作为队列使用。
LinkedList
实现了List
接口,可进行列表的相关操作。
LinkedList
实现了 Deque
接口,可作为队列使用。
LinkedList
实现了Cloneable
接口,可实现克隆。
LinkedList
实现了java.io.Serializable
接口,即可支持序列化,能通过序列化去传输。
HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步,是无序的,即不会记录插入的顺序
创建HashMap
的语法及常用方法是
HashMap<String, String> sites = new HashMap<String, String>();// 添加map元素sites.put("1", "google");sites.put("2", "Taobao");// 获取元素sites.get("2");// 移除元素 sites.remove("2");sites.clear(); // 全部移除for(Entry<String, String> entry : sites.entrySet()) { /* */ }for(String key : sites.keySet()) { /* */ }for(String value: sites.values()) { /* */ }
java.util.HashSet
是基于HashMap
类来实现的,是一个不允许有重复元素的集合, HashSet
是无序的,它不是线程安全的
<img src="https://www.runoob.com/wp-content/uploads/2020/07/java-hashset-hierarchy.png" alt="img" style="zoom:80%;" />
迭代器Java Iterator
不是一个集合,是一种访问集合的方法,可用于迭代ArrayList
和HashSet
等集合。
迭代器的基本操作是next()
, hasNext()
和remove()
java.util.Iterator
类是最简单的迭代器实现
ArrayList<String> cities = new ArrayList<String>();cities.add("ningbo");cities.add("shanghai");cities.add("beijing");Iterator<String> it = cities.iterator();while(it.hasNext()) { String city = it.next(); if(city.equals("beijing")) { it.remove(); continue; } System.out.println(city);}
根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像int,double,char的等)。
所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前
public static void main(String[] args) { Integer[] intArray = {1, 2, 3, 4, 5}; Double[] doubleArray = {1.1, 2.2, 3.3, 4.4}; String[] strArray = {"1", "2", "3", "4"}; printArray(intArray); printArray(doubleArray); printArray(strArray);}// 泛型方法声明! 泛型 <E> 放在返回值之前public static <E> void printArray(E[] inputArray) { for( E element : inputArray) { System.out.printf("%s ", element); } System.out.println();}
泛型方法中有界的类型参数: 用于限制允许船吊一个类型参数的类型种类范围,如一个泛型只希望接收Number
或者Number
的子类
在泛型类型后,使用关键字extends
,后续紧跟它的上界 就像类继承中的extends
表示泛型是扩展自某个基类
class MaximumClass { public static void main(String[] args) { Integer intMax = maximum(3, 4, 5); Double doubleMax = maximum(3.3, 4.4, 5.5); String strMax = maximum("hello", "hellO", "helloa"); System.out.println(intMax); System.out.println(doubleMax); System.out.println(strMax); } // 类型T必须继承自实现了Comparable接口的类 public static <T extends Comparable<T> > T maximum(T x, T y, T z) { T max = x; if ( y.compareTo(max) > 0 ) { max = y; } if ( z.compareTo(max) > 0 ) { max = z; } return max; }}
泛型类 : 泛型类的声明和非泛型类的声明类似,泛型类的类型参数声明部分包含一个或者多个类型参数,必须指定!
注意!在泛型类的声明中必须给定泛型参数!
class OuterClass<T> { private T t; public void add(T t) { this.t = t; } public T get() { return t; } public static void main(String[] args) { OuterClass<Integer> intOuterClass = new OuterClass<>(); OuterClass<String> strOuterClass = new OuterClass<>(); intOuterClass.add(3); strOuterClass.add("hello"); System.out.printf("整数值:%d \n", intOuterClass.get()); System.out.printf("字符串:%s \n", strOuterClass.get()); }}
类型通配符使用?
代替具体的参数,如List<?>
表示List<String>
List<Integer>
等所有List<具体类型实参>
的父类
注意是
List<具体类型实参>
的父类,不是具体类型实参的父类!
类型通配符可以结合extends
来限定具体类型参数的上限, 可以结合super
来限定具体类型参数的下限
import java.util.*;class OuterClass { // 参数中使用了类型通配符 public static void getData(List<?> data) { System.out.println("data :" + data.get(0)); } // 类型通配符可以结合extends来限定具体参数类型的上限 - 只接受该类型及其子类 public static void getUperNumber(List<? extends Number> data) { System.out.println("data: " + data.get(0)); } // 类型通配符结合super来限定具体参数类型的下限 - 只接受该类型及其父类 public static void getLowerNumber(List<? super Number> data) { System.out.println("data:" + data.get(0)); } public static void main(String[] args) { List<String> name = new ArrayList<>(); List<Integer> age = new ArrayList<>(); List<Number> number = new ArrayList<>(); name.add("hello"); age.add(3); number.add(3.3); getData(name); getData(age); getData(number); // getUperNumber(name); // 编译错误! getUperNumber(age); // getLowerNumber(age); // 编译错误! getLowerNumber(number); }}
对象序列化提供一种对对象序列化的机制,该机制下,一个对象可以表示为一个字节序列,序列中包含该对象的数据、对象的类型信息和存储在对象中的数据类型。被序列化的对象也可以被反序列化。
序列化及反序列化过程独立于Java虚拟机,这说明一个平台上序列化的对象可以在另一个不同的平台上被反序列化。
序列化成功的条件:
java.io.Serializable
接口transient
修饰符。注意: 当序列化一个对象到文件时, 按照 Java 的标准约定是给文件一个
.ser
扩展名。 借助FileOutputStream
和ObjectOutputStream
两个类用于序列化输出; 借助FileInputStream
和ObjectInputStream
两个类用于逆序列化
import java.util.*;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInput;import java.io.ObjectInputStream;import java.io.ObjectOutput;import java.io.ObjectOutputStream;import java.io.Serializable;class OuterClass implements java.io.Serializable{ public static void main(String[] args) { String filePath = "c:/Users/I334870/Desktop/employee.ser"; OuterClass outclass = new OuterClass(); try { Employee employee = outclass.new Employee("Hello", "Shanghai", 1334870, 101); // 借助FileOutputStream 和 ObjectOutputStream 两个类 FileOutputStream fileOut = new FileOutputStream(filePath); ObjectOutputStream out = new ObjectOutputStream(fileOut); out.writeObject(employee); out.close(); fileOut.close(); System.out.println("Serialized data is saved in your desktop"); } catch (IOException e) { e.printStackTrace(); } DeserializeDemo demo = outclass.new DeserializeDemo(); demo.deserializeEmployee(filePath); } class DeserializeDemo { public void deserializeEmployee(String filePath) { Employee employee = null; try{ // 借助FileInputStream 和 ObjectInputStream 两个类 FileInputStream fileIn = new FileInputStream(filePath); ObjectInputStream in = new ObjectInputStream(fileIn); employee = (Employee) in.readObject(); in.close(); fileIn.close(); } catch(IOException e) { e.printStackTrace(); return; } catch(ClassNotFoundException c) { System.out.println("Class Employee not found"); c.printStackTrace(); return; } System.out.println("Name = " + employee.name); System.out.println("Address = " + employee.address); System.out.println("SSN = " + employee.SSN); System.out.println("Number = " + employee.number);; } } class Employee implements java.io.Serializable { public String name; public String address; // 当对象被序列化时,属性SSN的值为1334870,但是因为该属性是短暂的,该值没有被发送到输出流。所以反序列化后Employee对象的SSN属性为0 public transient int SSN; public int number; public Employee(String name, String address, int SSN, int number) { this.name = name; this.address = name; this.SSN = SSN; this.number = number; } public Employee(){} public void mailCheck() { System.out.println("Mailling a check to " + name + " " + address); } }}
使用Java应用程序发送电子邮件,需要在机器上安装JavaMail API和Java Activation Framework(JAF)
下载后,需要把对应的mail.jar和activation.jar文件添加到CLASSPATH
中
import java.util.*;import javax.mail.*;import javax.mail.internet.*;import javax.activation.*;class OuterClass { public static void main(String[] args) { String sendTo = "hello@gmail.com"; String sendFrom = "me@gmail.com"; String host = "localhost"; // 获取系统属性 Properties properties = System.getProperties(); properties.setProperty("mail.smtp.host", host); Session session = Session.getDefaultInstance(properties); try { // 创建默认的MIME message对象 MimeMessage message = new MimeMessage(session); message.setFrom(new InternetAddress(from)); // 还可以在此设置CC, 或者BCC message.addRecipient(Message.RecipientType.TO, new InternetAddress(to)); message.setSubject("this is subject"); message.setContent("<h1>this is body msg</h1>", "text/html"); // 发送消息 Transport.send(message; } catch (MessageingException mex) { mex.printStackTrace(); } }}
java.net
包中的类和接口,提供了低层次的通信细节。该包支持TCP
和UDP
这两种常见的协议。
Socket编程 - TCP / Socket编程由java.net.socket
类完成。以下是两台计算机之间建立TCP连接过程的步骤
ServerSocket
对象,表示通过服务器上的端口通信;ServerSocket
类的accept()
方法,该方法将一直等待,直至客户端连接到服务器给定的端口;Socket
对象,指定服务器名和端口号来请求连接;Socket
类的构造函数将客户端连接到指定的服务器和端口,以建立通信;accept()
方法返回服务器上的一个新的Socket
对象,该Socket
对象连接到客户端的Socket
对象服务器应用程序通过使用 java.net.ServerSocket
类以获取一个端口,并且侦听客户端请求。
java.net.Socket
类代表客户端和服务器都用来互相沟通的套接字。客户端要获取一个 Socket
对象通过实例化 ,而 服务器获得一个 Socket
对象则通过 accept()
方法的返回值。
下面的代码演示阻塞式IO的网络编程(BIO),但是Java中还有NIO*(同步非阻塞式IO编程), AIO(异步非阻塞式IO编程)这点上类似Node.js
java.nio.channels
包下增加了四个异步通道:AsynchronousSocketChannel
,AsynchronousServerSocketChannel
,AsynchronousFileChannel
,AsynchronousDatagramChannel
// client socketimport java.io.DataInputStream;import java.io.DataOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.Socket;public class Client { public static void main(String[] args) { String serverName = args[0]; int port = Integer.parseInt(args[1]); try { System.out.println("Connecting to server " + serverName + ", port " + port); // create client socket, connect to server Socket client = new Socket(serverName, port); System.out.println("Remote server address:" + client.getRemoteSocketAddress()); // send messsage to server OutputStream outToServer = client.getOutputStream(); DataOutputStream out = new DataOutputStream(outToServer); out.writeUTF("hello, from " + client.getLocalSocketAddress()); // receive message from server InputStream inFromServer = client.getInputStream(); DataInputStream in = new DataInputStream(inFromServer); System.out.println("Server respondes:" + in.readUTF()); client.close(); } catch (IOException io) { io.printStackTrace(); } }}
// Server Socketimport java.io.DataInputStream;import java.io.DataOutputStream;import java.io.IOException;import java.net.ServerSocket;import java.net.Socket;import java.net.SocketTimeoutException;public class Server extends Thread { private ServerSocket serverSocket; public Server(int port) throws IOException { serverSocket = new ServerSocket(port); serverSocket.setSoTimeout(100000); } public void run() { while(true) { try { System.out.println("Waiting for clients on port:" + serverSocket.getLocalPort() + "..."); // create server socket - it waits until a client socket connects, but the maximum waiting time can be set Socket server = serverSocket.accept(); System.out.println("Remote address: " + server.getRemoteSocketAddress()); // receive message from client DataInputStream in = new DataInputStream(server.getInputStream()); System.out.println(in.readUTF()); // send message to client DataOutputStream out = new DataOutputStream(server.getOutputStream()); out.writeUTF("Welcome: " + server.getLocalSocketAddress() + "\n goodbye!"); server.close(); } catch (SocketTimeoutException timeout) { System.out.println("Socket timed out"); break; } catch (IOException io) { io.printStackTrace(); break; } } } public static void main(String[] args) { int port = Integer.parseInt(args[0]); try { Thread thread = new Server(port); thread.run(); } catch (IOException io) { io.printStackTrace(); } }}
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
进程 vs 线程:
进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。
线程:不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束
线程的状态:
使用new
和Thread
类或其子类建立一个线程,该线程对象就处于新建状态;
线程对象调用start()
方法后,该线程对象进入就绪状态,等待JVM里线程调度器的调度;
就绪状态的线程获取CPU资源,就可以执行run()
方法,该线程处于运行状态。该状态可以变为就绪、阻塞、死亡;
如果线程对象执行了睡眠sleep()
, 挂起suspend()
等方法,失去所占资源后,该线程进入阻塞状态;时机成熟后,再次进入就绪状态;
线程完成任务或其他终止条件出发,该线程就进入死亡状态。
Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY
) , 5 (NORM_PRIORITY
)^默认^, 10(Thread.MAX_PRIORITY
)
优先级的设置不能完全保证线程按照优先级被调度,具体由平台决定
线程的重要概念:
创建线程的3种方式
Runnable
接口Thread
类Callable
和Future
创建线程采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用
Thread.currentThread()
方法,直接使用 this 即可获得当前线程。
Runnable
接口创建线程最简单的方式创建一个线程,就是实现Runnable
接口,然后重写该接口的run()
import java.lang.Runnable;public class RunnableDemo implements Runnable { private Thread t; private String threadName; RunnableDemo(String name) { threadName = name; System.out.println("Creating " + threadName); } @Override public void run() { // JVM线程调度器调用线程的run(), 进入运行状态 System.out.println("Running " + threadName); try { for(int i = 4; i > 0; i--) { System.out.println("Thread: " + threadName + ", " + i); Thread.sleep(300); } } catch ( InterruptedException e) { System.out.println("Thread " + threadName + "interrupted."); } System.out.println("Thread " + threadName + " exiting."); } public void start() { System.out.println("Starting " + threadName); if (t == null) { // 新建状态 t = new Thread(this, threadName); // 就绪状态 t.start(); } }}
Thread
来创建线程创建一个线程的第二种方法是创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。
继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。
该方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例。
从代码上看,继承方式同实现方式创建线程的代码和过程是类似的!
public class ThreadDemo { private Thread t; private String threadName; public ThreadDemo(String name) { threadName = name; System.out.println("Thread " + threadName + " created"); } public void run() { System.out.println("Running " + threadName); try { for (int i = 4; i > 0; i--) { System.out.println("Thread: " + threadName + ", " + i); Thread.sleep(300); } } catch (InterruptedException e) { System.out.println("Thread " + threadName + "interrupted."); } System.out.println("Thread " + threadName + " exiting."); } public void start() { System.out.println("Starting " + threadName); if (t == null) { t = new Thread(this, threadName); t.start(); } }}
Callable
和Future
创建线程Callable
接口的实现类,实现call()
方法,该方法是线程的执行体,并且有返回值Callable
实现类的实例,使用FutureTask
类来包装Callable
对象,该FuturnTask
对象封装了该Callable
对象的call()
方法返回值FuturnTask
对象作为Thread
对象的target
创建并启动新线程FuturnTask
对象的get()
方法来获取子线程执行后的返回值相比于其他两种方式,这一个方式能够获取线程的返回值
import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;public class CallableThreadTest implements Callable<Integer> { public static void main(String[] args) { CallableThreadTest ctt = new CallableThreadTest(); FutureTask<Integer> ft = new FutureTask<>(ctt); for( int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " 循环变量i的值" + i); if ( i == 20 ) { new Thread(ft, "有返回值的线程").start(); } } try { System.out.println("子线程的返回值:" + ft.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch(ExecutionException e) { e.printStackTrace(); } } @Override public Integer call() throws Exception { int i=0; for(; i<100; i++) { System.out.println(Thread.currentThread().getName() + " " + i); } return i; }}
Java 连接 MySQL 需要驱动包,从官方下载后,解压后得到 jar 库文件,然后在对应的项目中导入该库文件。
import java.sql.Statement;import java.sql.Connection;import java.sql.DriverManager;import java.sql.ResultSet;import java.sql.SQLException;public class MySQLDemo { static final String JDBC_DRIVER = "com.mysql.jdbc.Driver"; static final String DB_URL = "jdbc:mysql:<host>:<port>/<db>"; static final String USER = "root"; static final String PASS = "123456"; public static void main(String[] args) { Connection conn = null; Statement stmt = null; try { // 注册JDBC驱动 Class.forName(JDBC_DRIVER); // 打开链接 System.out.println("连接数据库..."); conn = DriverManager.getConnection(DB_URL, USER, PASS); // 执行查询 System.out.println("实例化SQL命令对象..."); stmt = conn.createStatement(); String sql = "SELECT id, name, url FROM websites"; ResultSet rs = stmt.executeQuery(sql); // 展开结果集数据 while( rs.next() ) { int id = rs.getInt("id"); String name = rs.getString("name"); String url = rs.getString("url"); /* do something else*/ } rs.close(); stmt.close(); conn.close(); } catch (SQLException se) { se.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } finally { try { if (stmt != null) stmt.close(); } catch(Exception e) { /* do nothing */} try { if (conn != null) conn.close(); } catch (Exception e) { /* do nothing */} } System.out.println("ends"); }}
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。