Java语法基础(一)

类的源文件名必须与类同名。对于所有的类来说,类名的首字母应该大写。

如果没有显式为一个类指定构造方法,编译器会默认提供。

在创建一个类对象的时候,必须要有构造方法,类的构造方法名必须与类同名,且可以有多个。Java中使用new关键字创建对象。

一个Java的源文件只能有一个Public类,但可以有多个非Public的类。源文件的名称需与Public的类的类名保持一致。

Package

主要用来对Java中众多的类、接口进行分类。

如果一个类定义在某个包中,那么 package 语句应该在源文件的首行。

包名通常使用小写的字母来命名避免与类、接口名字的冲突。
创建包:

///创建 ` learn.cls`的包
package learn.cls; 
public class Person {
  
}
//在包中加入`Student`类,独立的源文件
package learn.cls;
public class Student extends Person {
}

包名必须要有与之对应的文件目录结构:
learn.cls则必须要有learn/cls这样的目录结构。

import

import + 路径 (包名,类名),用以定位类或源代码。
import learn.cls.* 编译器会从ClASSPATH/learn/cls目录下查找对应的类。

如果源文件包含 import 语句,那么应该放在 package 语句和类定义之间。如果没有 package 语句,那么import 语句应该在源文件中最前面。

import语句和 package 语句对源文件中定义的所有类都有效。在同一源文件中,不能给不同的类不同的包声明。

基本数据类型

Java有两大数据类型

  • 内置数据类型
  • 引用数据类型

内置数据类型

有8种基本数据类型:
四种整型:
byte: 8位有符号的整数 范围 -128 ~ 127
short: 16位有符号的整数 范围 -2^15 ~ 2^15 - 1
int: 32位有符号的整数 范围 -2^31 ~ 2^31 - 1
long: 64位有符号的整数 范围 -2^63 ~ 2^63 - 1
两种浮点数:
float: 单精度,32位 小数
double: 双精度,64位 小数
其他:
boolean : true or false
char : 单一的 16Unicode 字符

引用类型

对象、数组都是引用数据类型。
所有引用类型的默认值都是null
一个引用变量可以用来引用任何与之兼容的类型。

Java常量

Java 中使用final 关键字来修饰常量,声明方式和变量类似:

final double PI = 3.1415927;

byte、int、long、short都可以用十进制、16进制以及8进制的方式来表示。
当使用字面量的时候,前缀 0表示 8 进制,而前缀 0x代表 16 进制, 例如:

int decimal = 100;
int octal = 0144;
int hexa =  0x64;

字符串常量和字符常量都可以包含任何Unicode字符。例如:

char a = '\u0001';
String a = "\u0001";

自动类型转换

整型、实型(常量)、字符型数据可以混合运算。运算中,不同类型的数据先转化为同一类型,然后进行运算。
转换顺序由低到高依次为:

byte, short, char -> int->long->float->double

数据类型转换必须满足如下规则:

  1. 不能对boolean类型进行类型转换。
  2. 不能把对象类型转换成不相关类的对象。
  3. 在把容量大的类型转换为容量小的类型时必须使用强制类型转换。
  4. 浮点数到整数的转换是通过舍弃小数得到,而不是四舍五入.

强制类型转换

int x = 128;
byte y = (byte) x; // y = -128 位数不足,溢出
double d = 7.9D;
 int e = (int)d; //7

整数的默认类型为int;
小数默认是 double 类型,在定义 float 类型时必须在数字后面跟上 F 或者 f

Java的变量

Java中变量的声明与其他语言大同小异,基本都是 Type Identifier = value

局部变量

局部变量不会有默认值,访问修饰符也不能作用于局部变量,必须初始化才能使用

public void pupAge(){
      ///错误
      int age;  
     ///正确
      int age = 0;  
      age = age + 7;
      System.out.println("小狗的年龄是 : " + age);
   }

实例变量

当一个对象被实例化之后,每个实例变量的值就跟着确定;实例变量在对象创建的时候创建,在对象被销毁的时候销毁;实例变量可以声明在使用前或者使用后;访问修饰符可以修饰实例变量;实例变量具有默认值。数值型变量的默认值是0,布尔型变量的默认值是false,引用类型变量的默认值是null。变量的值可以在声明时指定,也可以在构造方法中指定;

Java的修饰符

Java中的修饰符有两种:

访问修饰符

用来对类、方法、构造方法、变量的访问设置不同的权限。

  1. public : 范围:所有类可见;修饰对象:类、方法、接口、变量
  2. protected : 范围:同一包中的类和所有子类可见,修饰对象:方法、变量;不能修饰类
  3. private:范围:同一类内可见,修饰对象:方法、变量;不能修饰类
  4. default:默认访问修饰符,不使用任何关键字;同一包中可见;修饰对象:类、接口、方法、变量。

接口里的变量都隐式声明为 public static final,而接口里的方法默认情况下访问权限为 public

protected修饰符有个两个点需要分析说明:

  • 子类与基类在同一个包内,被声明为 protected 的变量、方法和构造器能被同一个包中的任何其他类访问。
  • 子类与子类不在同一个包内,那么在子类中,子类实例可以访问其从基类继承而来的 protected 方法,而不能访问基类实例的protected方法。示例:
///基类在包`Animals `中
package Animals;
public class Animals {
    protected void run() {
        System.out.print("跑");
    }
    protected void  eat() {
        System.out.print("吃饭");
    }
}
///子类在包 learn.cls中
package learn.cls;
import Animals.Animals;
public class Dog extends Animals {
    @Override
    protected void eat() {
        this.run();
        System.out.print("吃屎");
    }
}

// 在包learn.cls的其他类中创建Dog实例并访问基类被protected修饰的方法
Dog dog = new Dog();
dog.eat(); ///✅
dog.run();//无法访问,编译器会报错 ❌
///但是基类的run方法在子类是可在直接访问的,

子类能访问 protected 修饰符声明的方法和变量,这样就能保护不相关的类使用这些方法和变量。如果我们只想让该方法对其所在类的子类可见,则将该方法声明为 protected

访问控制和方法继承规则

  • 父类声明的public方法,在子类中也必须为public;
  • 父类声明的protected方法,在子类中要么为Public,要么protected但不能为private
  • 父类声明的private方法,不能被子类继承;

非访问修饰符

  • static 修饰符,用来修饰类方法和类变量。
  • final 修饰符,用来修饰类、方法和变量,final 修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的。
  • abstract 修饰符,用来创建抽象类和抽象方法。如果一个类包含抽象方法,那么该类一定要声明为抽象类。
  • synchronizedvolatile 修饰符,主要用于线程的编程。

synchronized 关键字声明的方法同一时间只能被一个线程访问。synchronized修饰符可以应用于四个访问修饰符。

volatile 修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
一个 volatile 对象引用可能是null

///`synchronized `
public synchronized void showDetails(){
.......
}
///`volatile `
private volatile boolean active;

transient 修饰符:序列化的对象包含被 transient 修饰的实例变量时,java 虚拟机(JVM)跳过该特定的变量。即:用它修饰的变量被序列化不会被持久化。

抽象方法是一种没有任何实现的方法,该方法的具体实现由子类提供。抽象方法不能被声明成 finalstatic。任何继承抽象类的子类必须实现父类的所有抽象方法,除非该子类也是抽象类。
如果一个类包含若干个抽象方法,那么该类必须声明为抽象类。抽象类可以不包含抽象方法。
抽象方法的声明以分号结尾,例如:public abstract sample();

Java 运算符

Java中的运算符与其他语言运算符基本一致,分为:

  • 算数运算符
  • 自增/减运算符:a++、a--、++a、--a
  • 关系运算符
  • 位运算符: 与运算 同1 ? 1 : 0 或运算: 同0 ? 0 : 1 异或 位值相同 ? 0 : 1
  • 逻辑运算符
  • 赋值运算符
  • 条件运算符、三元运算符

特有的运算符:
instanceof 运算符: 该运算符用于操作对象实例,检查该对象是否是一个特定类型(类类型或接口类型)。 运算符使用的格式: ( Object reference variable ) instanceof (class/interface type)

boolean istrue = "abcd" instanceof  String; // result: true

Java的循环结构

Java中有三种主要的循环结构,和其他语言一致,分为:

  • while 循环
  • do…while 循环
  • for循环

其中for循环在Java中另一种使用方式:增强for循环:

int[] numbers = {1,2,3,4,5};
for(int item : numbers) {
    System.out.println(item);
}

Java的Number与Math

Numer:将基本数据类型的包装成类,java中的基本数据类型都有自己的包装类。

///左侧:基本数据类型
///右侧:包装后的类
byte -> Byte  //extends Number extends Object
short -> Short //extends Number extends Object
int -> Integer //extends Number extends Object
long -> Long //extends Number extends Object
float -> Float //extends Number extends Object
double -> Double //extends Number extends Object

boolean -> Boolean //extends Object
char -> Character //extends Object

Boolean boolobj = Boolean.TRUE;
Character charobj1 = 'e'; //e
Character charobj2 = '\u0065'; //e
System.out.println(charobj);

关于运算

Integer x = 5;
int y = 10
x =  x + y;
System.out.println(x);  //15

关于Math

int a = -10;
Integer b = -40;
a = Math.abs(a);
b = Math.abs(b);
a = a + b;
System.out.println(a); // 50

Java中的Character

Character 类用于对单个字符进行操作。
将一个char类型的参数传递给需要一个Character类型参数的方法时,那么编译器会自动地将char类型参数转换为Character对象

Character charobj1 = 'e'; //e
Character charobj2 = '\u0065'; //e
if (Character.isDigit(charobj1)) {
    System.out.println("是数字");
}
String str = Character.toString(charobj2);
System.out.println(str);//e

Java中的字符串

  • 创建字符串
String string = "String"; /// 存储在公共池
String heapStr = new String("String"); /// 存储在堆中
char[] chars = {'h','e','l','l','o'};
String heapStrByChar = new String(chars);
System.out.printf("公共池:%s,堆字符串:%s\n",string,heapStrByChar);//公共池:String,堆字符串:hello
  • 字符串操作
///获取字符串长度
int length = heapStrByChar.length();/// 5
///拼接两个字符串
string = heapStrByChar.concat(string); //Stringhello
///字符串格式化
String format = String.format("公共池:%s,堆字符串:%s\n",string,heapStrByChar);
System.out.println(format);

注意:String 类是不可改变的,所以一旦创建了 String对象,那它的值就无法改变了
如果需要改变需要用到StringBufferStringBuilder

///StringBuilder
///参数提示 cmd + p
StringBuilder sb = new StringBuilder(10);
sb.append("hello");
sb.insert(5,'!');
sb.append(new char[]{'B','o','b'});
System.out.println(sb);//hello!Bob
sb.delete(3,6);
String finalStr = sb.toString();
System.out.println(finalStr);///helBob

///StringBuffer
StringBuffer sbf = new StringBuffer("hello");
sbf.append('!');
sbf.append(new  char[]{'j','a','v','a'});
sbf.insert(0,"smile");
sbf.insert(5,' ');
System.out.println(sbf);//smile hello!java
sbf.deleteCharAt(11);
String res = sbf.toString();
System.out.println(res);//smile hellojava

StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。
由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。

Java数组

  • 数组声明
///首选
dataType [] arrayVar;
///效果一致,非首选
dataType arrayVar[]; 
  • 数组的创建
int[] array = new int[10];
array[0] = 1;
array[1] = 2;
int[] array = {1,2,3,4,4,5};
for (int x : array) {
    System.out.println(x);
}
  • 多维数组声明
///格式
type[][] typeName = new type[typeLength1][typeLength2];
///示例 2行3列的二维整型数组
int[][] intArray = new int[2][3];
  • 多维数组的创建
int[] arr = {1,2,3};
int[][] array1 = new int[2][3];
array1[1][0] = 2;
array1[1][1] = 3;
array1[1][2] = 4;
array1[0] = arr;
for (int[] item : array1) {
      for (int x : item) {
             System.out.println(x);
       }
  }
  • 通过Arrays类的类方法,执行对数组的操作
int[] array = {1,2,3,4,4,5};
int[] arr = {1,2,3};
Arrays.sort(array);
Arrays.equals(array,arr);
///...

Java日期

  • Date日期创建
Date date = new Date();
///y:1900 + 119 = 2019 ; m: 0~11 
Date date1 = new Date(119,10,20);
// 1$ 表示第一个参数 %t 表示打印时间 %n 换行 %tF 表示以格式`%tY-%tm-%td`输出日 %tb/%tB 月份的全称与简称
System.out.printf("%1$tY:%1$tm:%1$td %1$tB%n  %2$tF%n",date,date1);
///2022:02:17 二月
///2019-11-20
  • Date日期格式化
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String tfs = df.format(date);
System.out.println(tfs);
  • 字符串转日期Date
SimpleDateFormat df1 = new SimpleDateFormat("yyyy-MM-dd");
String dateStr = "2022-10-01";
try {
  Date date2 = df1.parse(dateStr);
  System.out.printf("%tD%n",date2);//10/01/22
} catch (ParseException e) {
   System.out.println("解析出错");
}
  • 日期格式化输出
    详见:https://www.runoob.com/w3cnote/java-printf-formate-demo.html

  • Calendar的创建与使用
    Calendar类是一个抽象类,在实际使用时使用其特定的子类的对象,创建对象的过程对程序员来说是透明的,只需要使用getInstance方法创建即可。

///默认当前日期
Calendar cld = Calendar.getInstance();
///可修改为表示2018年12月15日的日历对象
/// month : 0 ~ 11
cld.set(2018,11,15);
///将天数修改为10
cld.set(Calendar.DATE,10);
System.out.printf("%tF%n",cld.getTime());//2018-12-10
// 获得年份
int year = cld.get(Calendar.YEAR);
// 获得月份
int month = cld.get(Calendar.MONTH) + 1;
// 获得日期
int day = cld.get(Calendar.DATE);
System.out.printf("%s:%s:%s%n",year,month,day);
  • GregorianCalendar
    GregorianCalendarCalendar类的一个具体实现。CalendargetInstance()方法返回一个默认用当前的语言环境和时区初始化的GregorianCalendar对象。GregorianCalendar定义了两个字段:ADBC。这是代表公历定义的两个时代。
GregorianCalendar gcld = new GregorianCalendar();
GregorianCalendar gcld1 = new GregorianCalendar(2019,11,30,23,59,59);
System.out.printf("%tc%n",gcld1.getTime());//周一 12月 30 23:59:59 CST 2019
///其他与`Canlendar`一致

Java正则表达式

Java中的正则使用主要包括三个类:PatternMatcherPatternSyntaxException

///检索字符串是否包含与正则匹配的子字符串
String content = "wowaattterww";
///.* regex .* 用来查找字符串中是否包含被`regex`匹配到的字串
String regex = ".*wa{1,2}t{1,3}er.*"; //它匹配 waater/waatter/waattter等
boolean match = Pattern.matches(regex,content);
System.out.println(match);///true
  • 捕获组
    捕获组是把多个字符当一个单独单元进行处理的方法,它通过对括号内的字符分组来创建。例如,正则表达式 (dog) 创建了单一分组,组里包含"d","o",和"g"。
    捕获组是通过从左至右计算其开括号来编号。例如,在表达式:"((A)(B(C)))",有四个这样的组:((A)(B(C))))(A)(B(C))(C)
String content = "wowaattterww888.com";
///分4组,以左括号划界
/// 0 组 以`reg_p`为整体
//  1 组 只匹配`(\D*)`: 匹配非数字字符 0个或多个 与反向范围字符[^0-9]等效
//  2 组 只匹配 `(\d+)`: 匹配数字字符 1个或多个 与范围字符[0-9]等效
//  3 组 只匹配 `(.*)` : 匹配单个字符 0个或多个
String reg_p = "(\\D*)(\\d+)(.*)";
Pattern pattern = Pattern.compile(reg_p);
///只能通此来创建`Matcher`类
Matcher matcher = pattern.matcher(content);
//按照惯例,第0组表示整个模式。它不包括在此计数中。
System.out.println("该正则有"+ (matcher.groupCount() + 1) + "个分组\n");//该正则有4个分组
//Attempts to find the next subsequence of the input sequence that matches the pattern.
boolean found = matcher.find();///匹配到了
if (found) {
    System.out.printf("第一个捕获组的匹配到的值:%s 开始索引:%d,结束索引:%d%n",matcher.group(0),matcher.start(0),matcher.end(0));//wowaattterww888.com
    System.out.printf("第二个捕获组的匹配到的值:%s 开始索引:%d,结束索引:%d%n",matcher.group(1),matcher.start(1),matcher.end(1));//wowaattterww888.com
    System.out.printf("第三个捕获组的匹配到的值:%s 开始索引:%d,结束索引:%d%n",matcher.group(2),matcher.start(2),matcher.end(2));//wowaattterww888.com
    System.out.printf("第四个捕获组的匹配到的值:%s 开始索引:%d,结束索引:%d%n",matcher.group(3),matcher.start(3),matcher.end(3));//wowaattterww888.com
}
/*
第一个捕获组的匹配到的值:wowaattterww888.com 开始索引:0,结束索引:19
第二个捕获组的匹配到的值:wowaattterww 开始索引:0,结束索引:12
第三个捕获组的匹配到的值:888 开始索引:12,结束索引:15
第四个捕获组的匹配到的值:.com 开始索引:15,结束索引:19
*/
  • Matcher关键方法:lookingAtmatches
///lookingAt与matches
String content = "waattterwo"; ///
String regex = "wa{1,2}t{1,3}er"; //它匹配 waater/waatter/waattter等
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(content);
///输出:lookingAt匹配?true matches匹配?false
System.out.println("lookingAt匹配?"+ m.lookingAt() + " matches匹配?"+ m.matches() + "\n" );

lookingAtmatches方法的区别,matches要求整个字符串都要与正则匹配,而lookingAt不要求,但是它要求从字符串的开始位置就要与正则匹配,否则就是false。如果将上述代码:String content = "1waattterwo";,则lookingAt会返回false

  • Matcher关键方法:replaceFirstreplaceAll

replaceFirstreplaceAll方法用来替换正则表达式匹配到的文本。不同的是,replaceFirst替换首次匹配,replaceAll替换所有匹配。

String content = "waa3t2t2t5erwo";
String regex = "\\d+"; //匹配数字字符 1个或多个
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(content);
String firstReplace = m.replaceFirst("数字");
String allReplace = m.replaceAll("数字");
///输出:replaceFirst:waa数字t2t2t5erwo  replaceAll:waa数字t数字t数字t数字erwo
System.out.printf("replaceFirst:%s  replaceAll:%s%n",firstReplace,allReplace);
  • Matcher关键方法:appendReplacementappendTail
    Matcher 类也提供了appendReplacementappendTail 方法用于文本替换:
String content = "waa3t2t2t5erwo";
String regex = "\\d+"; //匹配数字字符 1个或多个
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(content);
StringBuilder sb = new StringBuilder();
while (m.find()) {
   m.appendReplacement(sb,"数字");
}
System.out.println(sb.toString());//waa数字t数字t数字t数字
m.appendTail(sb);
System.out.println(sb.toString());//waa数字t数字t数字t数字erwo

Java中的方法

大多与其他语言一致,区别如下:

  • 构造方法

构造方法和它所在类的名字相同,但构造方法没有返回值。
Java自动为所有的类提供了一个默认构造方法,默认构造方法的访问修饰符和类的访问修饰符相同(类为 public,构造函数也为 public;类改为 protected,构造函数也改为 protected)。一旦定义自己的构造方法,默认构造方法就会失效。

// 一个简单的构造函数
class MyClass {
  int x;
 
  // 以下是构造函数
  MyClass(int i ) {
    x = i;
  }
   MyClass() {
     x = 10;
  }
}
  • 可变参数
    格式:typeName... parameterName 可当做数组用

finalize() 方法

Java 9该方法被弃用了。
Java 允许定义这样的方法,它在对象被垃圾收集器析构(回收)之前调用,这个方法叫做finalize( ),它用来清除回收对象。

///` finalize( )`方法的一般格式
protected void finalize()
{
   // 在这里终结代码
}

关键字 protected 是一个限定符,它确保finalize()方法不会被该类以外的代码调用。

Java中的Stream、File、IO

Stream可以理解为一个数据的序列。输入流表示从一个源读取数据,输出流表示向一个目标写数据。

  • 控制台输入与输出
///接收控制台输入的输入流
InputStreamReader reader = new InputStreamReader(System.in);
char c;
do {
   c = (char) reader.read(); ///控制台输入完成后,读取控制台输入的字符,one by one
   ///输入 wwwwwwqq  输出 wwwwwwq
   System.out.println(c);
} while ( c != 'q');

///转一下,获取字符流,读取整行
BufferedReader bufferedReader = new BufferedReader(reader);
String readStr = bufferedReader.readLine();
System.out.println(readStr);
  • 文件读写
    创建一个文件写入数据,进行读取输出到控制台,然后删除该文件。
///创建输出流,写入项目根目录
File dir = new File("./test/tmp/");
dir.mkdirs();//会创建中间目录

///文件不存在,写入过程中会自动创建
File file = new File("./test/tmp/test.text");
try {
    ///此类写入的是字节流
    FileOutputStream fileOutputStream = new FileOutputStream(file);
    ///包装一层写入 编码后的字符串
    OutputStreamWriter writer = new OutputStreamWriter(fileOutputStream,"utf-8");
    writer.write("你好,");
    writer.append("我是鲍勃\n");
    ///关闭写入流
    writer.close();
    ///关闭输出流
    fileOutputStream.close();

    ///读取文件的内容
    FileInputStream fileInputStream = new FileInputStream("./test/tmp/test.text");
    ///读取解码后的解码字符串
    InputStreamReader reader = new InputStreamReader(fileInputStream,"utf-8");
    ///读取方式1;
    ///ready 文件不空 返回
    StringBuilder sb = new StringBuilder();
    while (reader.ready()){
        sb.append((char) reader.read());
    };
    System.out.println(sb.toString());

    ///读取方式2:
    ///返回文件预估长度,字节数
    ///int charLength = fileInputStream.available() / 2;
    ///char[] chars = new char[charLength];
    ///charLength = reader.read(chars); ///返回读到的字符数
    ///String res = new String(chars,0,charLength);
    ///System.out.println(res);

    ///关闭读取流
    reader.close();;
    ///关闭输入流
    fileInputStream.close();
    ///删除文件
    System.out.println(file.delete());
    ///删除目录 tmp
    dir.delete();
    ///删除父级目录 test
    dir.getParentFile().delete();
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (UnsupportedEncodingException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

Java中的Scanner类

Java 5新特性, 它也可以获取输入数据。

///文件输出
File file = new File("./test/tmp/test.text");
Scanner scanner = new Scanner(file);
///皆可
while (scanner.hasNext()) {
   System.out.println(scanner.next());///无法获取空格
}
while (scanner.hasNextLine()) {
   System.out.println(scanner.nextLine());///可以获取空格
}
scanner.close();
///获取用户键盘输入,进行控制台输出
Scanner scanner1 = new Scanner(System.in);
while (scanner1.hasNext()) {
   System.out.println(scanner1.next());
}
scanner1.close();

Java中异常

与其他语言,差别不大,具体细节:https://www.runoob.com/java/java-exceptions.html

Java中的继承

与其他语言差别不大,关键字extendsimplementsfinal

  • 构造方法
    子类是不继承父类的构造函数的。
    如果父类的构造器带有参数,则必须在子类的构造器中显式地通过super 关键字调用父类的构造器并配以适当的参数列表。
    如果父类构造器没有参数,则在子类的构造器中不需要使用super关键字调用父类构造器,系统会自动调用父类的无参构造器。
class SuperClass {
  private int n;
  SuperClass(){
    System.out.println("SuperClass()");
  }
  SuperClass(int n) {
    System.out.println("SuperClass(int n)");
    this.n = n;
  }
}
// SubClass 类继承
class SubClass extends SuperClass{
  private int n;
  SubClass(){ // 自动调用父类的无参数构造器
    System.out.println("SubClass");
  }  
  public SubClass(int n){ 
    super(300);  // 调用父类中带有参数的构造器
    System.out.println("SubClass(int n):"+n);
    this.n = n;
  }
}
// SubClass2 类继承
class SubClass2 extends SuperClass{
  private int n;
  SubClass2(){
    super(300);  // 调用父类中带有参数的构造器
    System.out.println("SubClass2");
  }  
  public SubClass2(int n){ // 自动调用父类的无参数构造器
    System.out.println("SubClass2(int n):"+n);
    this.n = n;
  }
}
  • implements
    使用implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。
public interface A {
    public void eat();
    public void sleep();
}
public interface B {
    public void show();
}
public class C implements A,B {
}
  • final
    final 关键字声明类可以把类定义为不能继承的,即最终类;或者用于修饰方法,该方法不能被子类重写。

Java的重写(Override)与重载(Overload)

  • 重写(Override)
    重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
    重写的规则:
    1.返回类型为被重写方法的返回类型及其子类。
    2.访问权限不能比父类中被重写的方法的访问权限更低。
    3.声明为 static 的方法不能被重写,但是能够被再次声明。
    4.构造方法不能被重写。
    5.重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
  • 重载(Overload)
    在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
    每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
    最常用的地方就是构造器的重载。

Java多态

三要素继承、重写、父类指针指向子类。与其他语言一样。

Java抽象类&抽象方法

关键字abstract
规则:
1.抽象类,不能被实例化;
2.抽象类可以没有抽象方法,但有抽象方法的类必定要是抽象类
3.抽象类中定义的抽象方法,必须要被子类 重写,除非子类也是抽象类
4.构造函数、Static修饰的函数,不能为抽象方法。
5.抽象方法只包含一个方法名,而没有方法体

Java 接口

Java接口是抽象方法的集合,关键字interface;一个类通过继承接口的方式,来继承接口中的抽象方法。接口和类是不同的概念。类实现接口,而非继承接口。

Java中接口可以用来声明变量,可指向空指针,也可以指向实现次接口的实例对象。

实现接口的类必须实现接口的所有方法,除非该类为抽象类。

接口特性:
1.接口中的每个方法都是隐式抽象;接口在声明的时候也是隐式抽象的; 隐式指定为public abstract
2.接口中可以有变量,但只能是public static final修饰的变量。

  1. Java8中可以定义接口的默认方法实现(有方法体),关键字default
  2. Java8中可以定义static方法实现。

实现接口的关键字: implements,接口实现的规则:
1.类在实现接口方法时,无法强制抛出异常;可在接口中或实现接口的抽象类中抛出该强制异常。
2.一个类可以实现多个接口。
3.一个接口可以继承另一个接口。

示例:

public interface Animals {
      void behavior();
      void eat();
      void die();
}

public interface Beasts extends Animals{
    ///定义接口方法
    void run();
    ///定义默认方法实现
    default void behavior() {
        System.out.printf("%s行为:地上跑%n", Beasts.BEASTS);
    }
    ///定义变量
    static final String BEASTS = "走兽";
    ///定义静态方法
    static String category() {
        return BEASTS + "类";
    }
}
//可多继承,比如 `public interface Birds extends Animals,Beasts`
public interface Birds extends Animals {
    ///定义接口方法
    void fly();

    ///默认实现`Animals`的方法
    default void behavior() {
        System.out.printf("%s行为:天上飞%n", BIRDS);
    }
    ///定义变量
    static final String BIRDS = "飞禽";
    ///定义静态方法
    static String category() {
        return BIRDS + "类";
    }
}
///飞虎!哇
public class FlyTiger implements Beasts, Birds {
    @Override
    public void behavior() {
        Birds.super.behavior();
        Beasts.super.behavior();
        System.out.println("飞虎行为怪异\n");
    }

    @Override
    public void eat() {
        System.out.println(Beasts.category()+":飞虎吃饭");
        System.out.println(Birds.category()+":飞虎吃饭");
    }

    @Override
    public void run() {
        System.out.println("飞虎在地");
    }

    @Override
    public void die() {
        System.out.println("飞虎在地下");
    }
    @Override
    public void fly() {
        System.out.println("飞虎在天");
    }
}

///使用
public static void main(String[] args) {
    Birds birds = new FlyTiger();
     birds.fly();
     Beasts beasts = new FlyTiger();
     beasts.run();
     FlyTiger flyTiger = new FlyTiger();
     flyTiger.eat();
     flyTiger.behavior();
     flyTiger.die();
}

Java 枚举

  • 枚举声明
enum Color {
    BLUE,
    RED,
    GREEN
}
  • 枚举使用
enum Color {
    BLUE,
    RED,
    GREEN
}
public class EnumTest {
    enum Version {
        DEV,
        RELEASE,
        TRAIL
    }
    public static void main(String[] args) {
        Color color = Color.RED;
        Version version = Version.DEV;
        System.out.println(version);///DEV
        System.out.println(color);///RED
    }
}
  • 枚举的内部实现
enum Color {
    BLUE,
    RED,
    GREEN
}
///内部通过`class`实现
class Color {
    public static final Color RED = new Color();
    public static final Color BLUE = new Color();
    public static final Color GREEN = new Color();
}
  • 关键方法
for (Version v : Version.values()) {///Version.values() 枚举的所有值
     System.out.println(v);
     System.out.println(v.ordinal());//索引
     System.out.println(Version.valueOf("DEV"));//取值
}

枚举跟普通类一样可以用自己的变量、方法和构造函数,构造函数只能使用 private 访问修饰符,所以外部无法调用。

public class EnumTest {
    enum Version {
        DEV,
        RELEASE,
        TRAIL;
        private Version(){
            System.out.println("Version枚举构造函数被"+this.name() + "调用\n");
        }
        public String getInfo() {
            return "版本信息" + this.name();
        }
    }
    public static void main(String[] args) {
        Version version = Version.RELEASE;///构造函数被调用
        String info = version.getInfo();///信息打印
        System.out.println(info);
    }
}
  • 枚举可实现抽象方法
enum Color {
    BLUE{
        public  String getInfo() {
            return "蓝色";
        }
    },
    RED{
        public  String getInfo() {
            return "红色";
        }
    },
    GREEN{
        public  String getInfo() {
            return "绿色";
        }
    };
    public abstract String getInfo();
}

public class EnumTest {
    public static void main(String[] args) {
       for(Color color : Color.values()) {
           System.out.println(color.getInfo());
       }
    }
}

Java集合框架

Java集合框架主要包括两种类型的容器:集合(Collection)、键值对(Map)。
Collection接口有3种子类型ListSetQueue也是接口。具体可实例化的集合类继承了实现上述接口的抽象类。
集合中存储Java对象。常用的可实例化的集合类:ArrayListLinkedListHashSet。常用的Map可实例化的类:HashMap

ArrayList

ArrayList类是一个可以动态修改的数组,与普通数组的区别就是它是没有固定大小的限制,我们可以添加或删除元素。

  • ArrayList的继承与接口实现关系
///ArrayList的继承与接口实现关系
public interface Collection<E> extends Iterable<E>
public interface List<E> extends Collection<E>
public abstract class AbstractCollection<E> implements Collection<E>
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  • ArrayList的关键操作
ArrayList<Integer> arrayList = new ArrayList<>();///创建
arrayList.add(100);///添加对象
arrayList.add(300);
arrayList.add(200);
///小->大排序
arrayList.sort((o1, o2) -> {
    return o1.compareTo(o2);
});
Collections.sort(arrayList);///小->大
///修改
arrayList.set(1, 250);
///查询
arrayList.get(0);
///删除
arrayList.remove(1);
///删除所有
arrayList.clear();
///遍历一
for (int i = 0; i < arrayList.size(); i++) {
    System.out.println(arrayList.get(i));
}
///遍历二
for (Integer i : arrayList) {
    System.out.println(i);
}
///遍历三:可以利用iterator.remove()方法删除元素
Iterator<Integer> iterator = arrayList.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

LinkedList

LinkedList:链表,是一种常见的数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的地址。链表可分为单向链表和双向链表。与ArrayList类似,但LinkedList 的增加和删除的操作效率更高,而查找和修改的操作效率较低。
以下情况使用 ArrayList:
1.频繁访问列表中的某一个元素。
2.只需要在列表末尾进行添加和删除元素操作。
以下情况使用 LinkedList :
1.需要通过循环迭代来访问列表中的某些元素。
2.需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素操作。

  • LinkedList的继承与接口实现关系
public interface Collection<E> extends Iterable<E>
public interface List<E> extends Collection<E>
public abstract class AbstractCollection<E> implements Collection<E>
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>
///多了一层
public abstract class AbstractSequentialList<E> extends AbstractList<E>
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
  • LinkedList的关键方法
///创建
LinkedList<String> linkedList = new LinkedList<>();
///添加
linkedList.add("java");
linkedList.add("flutter");
linkedList.add("objc");
linkedList.add("swift");
linkedList.add("shell");
///大-> 小 排序
linkedList.sort((o1, o2) -> {
    return o2.compareTo(o1);
});
Collections.sort(linkedList);///小->大
///修改
linkedList.set(4,"php");
///删除
linkedList.remove("php");
//linkedList.remove(3);
///查询
linkedList.get(0);
///其他
linkedList.addFirst("vue");
linkedList.addLast("js");
linkedList.removeFirst();
linkedList.removeLast();
///遍历一
for (int i = 0; i < linkedList.size(); i++) {
    System.out.println(linkedList.get(i));
}
///遍历二
for (String str :
        linkedList) {
    System.out.println(str);
}
///遍历三: 可以利用iterator.remove()方法删除元素
Iterator iterator = linkedList.iterator();
while (iterator.hasNext()){
    System.out.println(iterator.next());
}

HashSet

HashSet无序的,不允许有重复元素的集合,它不是线程安全的。如果多个线程尝试同时修改HashSet,则最终结果是不确定的。 必须在多线程访问时显式同步对 HashSet 的并发访问。

  • HashSet的继承与接口实现关系
public interface Collection<E> extends Iterable<E>
public interface Set<E> extends Collection<E>
public abstract class AbstractCollection<E> implements Collection<E>
public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E>
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable
  • HashSet关键方法
///创建
HashSet<String> hashSet = new HashSet<>();
///添加
hashSet.add(null);//可添加null
hashSet.add("张飞");
hashSet.add("关羽");
hashSet.add("刘备");
hashSet.add("赵云");
hashSet.add("赵云");//无法添加重复元素
///删除
hashSet.remove("赵云");
///删除所有
hashSet.clear();
///包含
if (hashSet.contains("刘备")) {
    System.out.println("包含");
}
///遍历
for (Object obj: hashSet) {
    System.out.println(obj);
}

HashMap

HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
HashMap 实现了 Map 接口,根据键的 HashCode值存储数据,具有很快的访问速度,最多允许一条记录的键为null,不支持线程同步。
HashMap 是无序的,即不会记录插入的顺序。
HashMapkeyvalue 类型可以相同也可以不同,可以是字符串(String)类型的 keyvalue,也可以是整型(Integer)的 key 和字符串(String)类型的 value

  • HashMap的继承与接口实现关系
public interface Map<K, V> 
public abstract class AbstractMap<K,V> implements Map<K,V>
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
  • HashMap的关键方法
///创建
HashMap<Integer,String> map = new HashMap<Integer,String>();
///添加
map.put(1,"1");
map.put(2,"2");
map.put(3,"3");
///如果没有则添加
map.putIfAbsent(2,"4");
///修改
map.replace(2,"20");
map.put(2,"22");
///删除
map.remove(3);
//获取
map.get(2);///22
System.out.println(map);
///遍历一
for (Integer key : map.keySet()) {
    System.out.println(map.get(key));
}
///遍历二
for (String value:map.values()) {
    System.out.println(value);
}
///遍历三
Iterator<Map.Entry<Integer,String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next().getValue());
}
///遍历四
ArrayList arrayList =  new ArrayList<>(map.values());
for (int i = 0; i < arrayList.size(); i++) {
    System.out.println(arrayList.get(i));
}

Java中的Object

JavaObject 类是所有类的父类,也就是说 Java 的所有类都继承了 Object,子类可以使用 Object 的所有方法。
Object类可以显示继承,也可以隐式继承,以下两种方式时一样的:

///显式继承
pulic class Test extends Object {
}
///隐式继承
public class test {
}

Java中的泛型

泛型方法

定义泛型方法的规则:
1.泛型方法需要声明类型参数,类型参数声明在函数返回值类型的前面,采用尖括号< >包裹。形如:<E>
2.类型参数声明可有多个,尖括号内,彼此用逗号,隔开
3.类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
4.泛型方法体的声明和其他方法一样。
5.注意类型参数只能代表引用型类型,不能是原始类型(像 int、double、char 等)。

///示例
public static <T,S>  T genericsFunc(T param1, S param2) {
    System.out.println(param2);
    return  param1;
}

泛型类

类名后面添加了类型参数声明部分,类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。

public class GenericTest<T,U> {
    T type;
    U user;
    public GenericTest(T type, U user){
        this.type = type;
        this.user = user;
    }
    public T getType(){
        return this.type;
    }
    public U getUser(){
        return  this.user;
    }
    public void setType(T type) {
        this.type = type;
    }
    public void setUser(U user) {
        this.user = user;
    }
    public static void main(String[] args) {
        GenericTest<String,String> genericTest = new GenericTest<>("工人","张三");
        genericTest.setType("农民");
        System.out.println(genericTest.type);
        System.out.println(genericTest.user);
    }
}

类型通配符与条件限定

类型通配符一般是使用 ? 代替具体的类型参数。例如 List<?>在逻辑上是 List<String>,List<Integer> 等所有 List<具体类型实参> 的父类。不需要额外声明的类型参数。

public static void printArray(List<?> arr) {
    System.out.println(arr.get(0));
}

public static void main(String[] args) {
    ArrayList list<String> = new ArrayList<String>();
    list.add("hello");
    printArray(list);
}
  • 限定通配符的类型为特定类型的子类: <? extends T>
public static void printArray(List<? extends Number> arr) {
    System.out.println(arr.get(0));
}

public static void main(String[] args) {
    ArrayList<String> list = new ArrayList<String>();
    list.add("hello");
    //printArray(list);//报错
    ArrayList<Integer> numlist = new ArrayList<Integer>();
    numlist.add(100);
    printArray(numlist);
}
  • 限定通配符的类型为特定类型的父类: <? super T>
public static void printArray(List<? super Integer> arr) {
    System.out.println(arr.get(0));
}

Java序列化

Java序列化,是将一个Java类对象序列化为字节序列的过程,该字节序列会保存对象的数据,也会保存对象类型关联的所有类型的信息;因此该字节序列也可以通过反序列化转化为对象。整个过程基于JVM,因此该字节序列支持平台迁移。

Java的对象支持序列化的条件,该对象所属类型必须实现接口java.io.Serializable

Java 序列化机制的主要类ObjectOutputStreamObjectInputStream

  • 序列化
    当序列化一个对象到文件时, 按照 Java 的标准约定是给文件一个 .ser 扩展名。
public class SerializableTest implements java.io.Serializable {
    public String name;
    public String job;
    public int age;
    public boolean hasJob;
    public transient String intro; ///无法被序列化
}
public static void serialization() {
    ///创建对象
    SerializableTest testObjc = new SerializableTest();
    testObjc.name = "波波";
    testObjc.job = "售货员";
    testObjc.age = 20;
    testObjc.hasJob = true;
    testObjc.intro = "还岁月以文明";
    try {
        FileOutputStream fileOutputStream = new FileOutputStream("/tmp/test.ser");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        ///对象序列化
        objectOutputStream.writeObject(testObjc);
        fileOutputStream.close();
        objectOutputStream.close();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

  • 反序列化
public static SerializableTest deserialization() {
    SerializableTest test = null;
    FileInputStream fileInputStream = null;
    try {
        fileInputStream = new FileInputStream("/tmp/test.ser");
        ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
        ///反序列化
        test = (SerializableTest) inputStream.readObject();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
    return test;
}

public static void main(String[] args) {
    serialization();
    SerializableTest obj = deserialization();
    if (obj != null) {
        System.out.println(obj.name);//波波
        System.out.println(obj.job);//售货员
        System.out.println(obj.age);//20
        System.out.println(obj.hasJob);//true
        System.out.println(obj.intro); ///null
    }
}

Java多线程

线程状态

创建->就绪(进入就绪队列,待JVM调度)->运行(获取CPU资源)->死亡;
运行状态下,线程可能因为调用sleepwaitsuspend进入阻塞状态。当睡眠时间到或重获CPU资源便会进入就绪状态。主要有三种阻塞:
1.线程运行时调用wait方法进入等待阻塞。
2.同步阻塞,同步锁:synchronized
3.其他阻塞,如:sleepjoin(等待线程终止或超时或I/O处理完毕)。

线程优先级

Java中线程的优先级0~10,如果不设置优先级默认为5

线程创建

  1. 通过实现Runnable接口创建线程

创建一个实现Runnable接口的类,该类需创建一个Thread实例,运行该Thread实例的start()方法,让线程执行。

一个实现了Runnable的类可以通过实例化一个Thread实例并将自己作为目标传入,从而在不子类化Thread的情况下运行。在大多数情况下,如果你只打算覆盖run()方法而不覆盖其他的Thread方法,那么就应该使用Runnable接口。

public class ThreadTest {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());///main
        startNewThread("线程1");
    }
    public static void startNewThread(String threadName) {
        ///创建一个实现`Runnable`接口的类
        Runnable runnable = new Runnable() {
            ///实现`run`方法
            ///获取CPU资源运行任务
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());///线程1
                try {
                    ///阻塞 5s
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(threadName +"休眠结束");///线程1休眠结束
            }
        };
        ///创建线程
        Thread t = new Thread(runnable,threadName);
        ///就绪,加入线程队列,
        t.start();
    }
}
  1. 通过继承Thread来创建线程
public class ThreadTest extends Thread {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());///main
        ThreadTest customThread = new ThreadTest("线程1");//线程1
        customThread.start();
    }

    public ThreadTest(String name) {
        super(name);
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());///线程1
        try {
            ///阻塞 5s
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(this.getName() +"休眠结束");///线程1休眠结束
    }
}
  1. 通过CallableFuture创建线程
public class ThreadTest {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());///main
        ///创建一个实现`Callable`接口的类
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return 100;
            }
        };
        ///使用`FutureTask`包装
        FutureTask<Integer> futureTask = new FutureTask<Integer>(callable);
        //创建线程,并将`FutureTask`作为线程执行对象
        Thread thread = new Thread(futureTask);
        ///使线程处于就绪状态
        thread.start();
        try {
           ///取值
            Integer integer = futureTask.get();
            System.out.println(integer);///100
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容