目录
前言
爪哇哇哇哇笔记!!
由于学习任务要求,又要写第二篇的爪哇学习笔记,都是一些爪哇基础,但这篇文章是上篇文章往后的内容。主要写的内容是爪哇的类与对象部分(上篇文章这是一篇关于爪哇(java)的学习文章-CSDN博客写的都是前置基础知识,感兴趣的同学可以去看一看。)
(本文把所有的java都写成了爪哇,看不下去不要打我)
类与对象
说到类与对象,首先我们就来聊聊他们最本质的区别。 通过资料我们可以找到,类是对象的抽象形态,对象则是类的具体实例。单单看文字解释可能还有点抽象,于是我们写了一点例子;
class Animal { //我是类
int age;//年龄
String name;//名字
String sex;//性别
}
上面就是我们的“Animal”类,单词不知道有没有打错;可以看到有3个属性,age年龄、name名字、sex性别。要想对Animal类的这些属性进行一个操作,我们就得建立一个实例化的对象,来给这3个进行一一赋值,于是
public static void main(String[] args) {
Animal animal = new Animal(); //我是对象
animal.age = 10;
animal.name = "二狗";
animal.sex = "男";
}
我们在上面这段mian函数中(这是程序的运行函数,我们的执行代码都是写在这里的),实例化了一个“Animal”类的“animal”,通过这样的方式我们就可以如代码所表示,一一给下面的3个属性进行赋值操作。通过这俩段代码的操作可以发现,有了“Animal”类这一个模板,对象“animal”才可以根据模板一一给模板的各个属性附上值,基于类定义。这便是类与对象的区别。
方法
什么是方法
1、普通方法\类的方法:把一段需要重复使用的代码用一个瓶子装起来,需要使用的时候通过"方法名()"将瓶子内的方法取出使用
访问修饰符 返回值类型 方法名字(){
}
public void Nihao() {
//公开 无返回 方法名
//方法体
}
2、构造方法:创建类时被调用的方法,可以更加方便的实例化对象的各个属性。(这个的例子我们后面举) 封装方法的快捷键:Ctrl+Alt+M
System.out.println("我来咯");
System.out.println("我走咯");
这是我们最开始的一串代码,我们对他进行封装方法的快捷键;
extracted1();//在原来sout语句的位置
....
....此处省略很多其他的代码
}
private static void extracted1() {
System.out.println("我来咯");
System.out.println("我走咯");
}
可以看到,我们的代码被封装成了一个方法,并且还为我们自动生成了一个方法名,这个时候我们只要对方法名进行修改后,按下回车键,就封装方法成功了。以后我们在需要打印这俩句“我来了”“我走了”的话的时候,就可以调用这个方法来直接使用。多次重复使用时,这样封装方法,就很好的减少了代码的冗余,增加了代码的可读性。
举这样的打印语句例子还是太抽象了......在实际书写代码的场合,比如说有个作业,我们需要对一个数组进行多次的操作,每次操作过后都需要将数组遍历打印在控制台上,用这种方式来展现数组发生的变化。这个时候我们就可以把这个数组的遍历打印语句,封装成一个方法,在每次需要打印数组内容时,调用这个方法,这样就可以让代码看起来简洁明了很多。
在写这(封装方法的快捷键:Ctrl+Alt+M)部分的时候,由于平时没有使用这个方便的快捷键,然后有天发现这个快捷键不能使用了QAQ
搜索了一下才发现,原来是我电脑上安装的NVIDIA GeForce与idea的快捷键冲突。
如果你也和我一样不能使用这个快捷键封装方法的话,可以通过Alt+Z快捷键打开NVIDIA GeForce,点设置图标——“键盘快捷键”——点击“切换话筒的开\关状态”旁边的框框,通过backspace让快捷键失效,这样就可以使用了。
访问修饰符
爪哇中的访问修饰符用于定义类、方法和变量的可见性。以下是爪哇中的四个访问修饰符:
- public:公有的。如果一个类、方法或变量被声明为public,那么它们可以在任何地方被访问。
- protected:受保护的。如果一个类、方法或变量被声明为protected,那么它们只能在其本身类中、子类中或者相同包中的其他类中被访问。
- default (package-private):默认的(包私有)。如果一个类、方法或变量没有被声明为public或protected,那么它们只能在相同的包中的其他类中被访问。
- private:私有的。如果一个类、方法或变量被声明为private,那么它们只能在它本身的类中被访问。
返回类型
返回类型是方法返回的数据类型。方法的返回类型可以是任何有效的数据类型,例如int、double、String、boolean等。如果一个方法不返回任何值,那么其返回类型应该是void。
参数列表
参数列表定义了传递给方法的参数。参数列表中的每个参数都由参数类型和参数名称组成。参数是可选的,也就是说,方法可以不包含参数。参数的类型可以是任何有效的数据类型,包括数组和接口。在参数列表中,每个参数之间用逗号隔开。
普通方法
class Animal {
int age;//年龄
String name;//名字
String sex;//性别
public void eat(){
System.out.println("吃东西");
}
public void eatB(){
System.out.println(name + "在吃东西");
}
}
上面的方法eat()和方法eatB(),就是我们的俩个类的方法。上面的eat()方法只是简单的打印一条语句,而后面的eatB()方法,则是带上了当前name对象,告诉了别人到底是谁在做这个吃东西的操作。当然由于只是俩个打印语句的方法,所以说它们都没有返回值,所以说都是void。
public double add(double num1,double num2) {
return num1 + num2;
}
上面的方法add做的事情就是将收到的参数列表num1和num2相加后,返回回去。因此返回值的类型是double,要接收add这个方法返回的值,需要一个double的对象,或根据自己的需求直接把值打印在控制台上System.out.printly(add(1,2));,通过这样的方式打印出来值为3.
public double add(double... num) {
if (num.length == 1)
return num[0];
double result = num[0];
for (int i = 1; i < num.length; i++) {
result += num[i];
}
return result;
}
当然,我们引用的参数列表,还可以通过double... num的形式,来表示有很多个double的数,进入方法后会得到一个数组对象num,通过这样的方式,我们就可以实现多个数的相加方法。
构造方法
class Animal {
int age;//年龄
String name;//名字
String sex;//性别
public Animal() {
//无参构造
}
public Animal(int age, String name, String sex) {
this.age = age;
this.name = name;
this.sex = sex;
}
}
以上便是先前我们Animal类的构造方法,可以看到,构造方法的方法名是和类名是一样的。构造方法是没有返回值类型的。类是会自动生成一个无参的构造方法的。但是当我们写了构造方法以后,就只能调用自己写的构造方法,要想调用无参的构造方法,就需要自己再写一个。
类的封装
封装是一种将数据(变量)和与数据相关的代码(方法)打包到一个类中的机制。这种机制可以保护数据不被外部直接访问,只能通过特定的方法来操作数据,这样可以增强数据的安全性和稳定性。同时,封装也使得代码更加模块化,易于维护和修改。
封装用法
- 私有变量:通常,我们将类的数据成员(变量)设为私有(private),这样外部的类就不能直接访问这些变量。
- 公共方法:我们提供公共方法(public method)来获取或设置私有变量的值。这些方法通常被称为“getter”和“setter”。
- 保护方法:有时,我们还需要提供一些保护方法(protected method),这些方法只有同一包中的类或者子类才能访问。
public class 封装 {
// 将成员变量设为私有,外界无法直接访问
private String name;
private int age;
private String sex;
// 公共构造函数
public 封装(String name, int age) {
this.name = name;
this.age = age;
}
}
在这个例子中,类“封装”的所有的属性都被封装起来了,不能通过'对象名.类名'的方式来直接修改。这个时候我们可以通过alt+insert的快捷键来快速生成getter(取值)和setter(赋值)方法.
public class 封装 {
// 将成员变量设为私有,外界无法直接访问
private String name;
private int age;
private String sex;
// 公共构造函数
public 封装(String name, int age) {
this.name = name;
this.age = age;
}
// “getter”方法,获取name的值
public String getName() {
return name;
}
// “setter”方法,设置name的值
public void setName(String name) {
this.name = name;
}
// “getter”方法,获取age的值
public int getAge() {
return age;
}
// “setter”方法,设置age的值
public void setAge(int age) {
this.age = age;
}
}
要获取当前对象的年龄age,就要调用当前对象的getAge()方法,要修改当前对象的年龄age,就要调用当前对象的setAge()方法.
封装 a = new 封装("Neko",21);
System.out.println("名字是:" + a.getName());
System.out.println("年龄是:" + a.getAge());
a.setAge(22);
System.out.println("修改后的年龄是:" + a.getAge());
//输出结果
名字是:Neko
年龄是:21
修改后的年龄是:22
在main函数输入如上代码后,就可以获取封装内的值,如上图的代码和运行结果所示。
类的继承
类的继承是通过使用“extends”关键字来实现的。子类可以继承父类的属性和方法,并且可以在此基础上添加自己的特性。
public class Cat extends Animal {
}
重写
重写方法名要一样,方法返回值要一样 基本数据类型必须一样 引用数据类型可以不一样,重写后的方法用@Override备注
重写规则
- 修饰词:子类权限等于大于父类权限
- 返回值:基本数据类型和void必须一致
- 方法名:参数列表必须一致
- 方法体:重写后要与原来的方法不同,不然重写将没有意义,直接调用就好了
例子:见后面的抽象类和接口的例子...
抽象类和接口
类、抽象类、接口的特点与区别
- 类:用class修饰 可以建对象
- 抽象类:用abstract 不能建对象 可以建子类对象
- 接口类:用interface 不能建对象 可以建子类对象 抽象类
- 定义:含有的方法没具体的实现(没有方法体的方法)的类,被称为抽象类
特点
- 方法和类都含有abstract关键字
- 抽象方法没方法体(没有大括号{})
- 抽象类是可以被继承的,子类继承后必须要重写父类的抽象方法,如果子类不重写,则该子类必须是抽象类。
public abstract class ChouXian{
public abstract void FanFa();
}
上方代码就是我们的一个抽象类ChouXian,和一个抽象方法Fanfa()。
例子:抽象Animal,继承抽象类的Cat和Dog类
Animal类
public abstract class Animal{
String name;
int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public abstract void eat();
public abstract void sleep();
public void work() {
System.out.println("在工作");
}
}
Cat类:重写eat和sleep方法
public class Cat extends Animal {
public Cat(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("猫在吃东西");
}
@Override
public void sleep() {
System.out.println("猫在睡觉");
}
}
Dog类:重写eat和sleep方法
public class Dog extends Animal {
public Dog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("狗在吃东西");
}
@Override
public void sleep() {
System.out.println("狗在睡觉");
}
}
由于Animal类的抽象方法的eat和sleep是没有实际的方法体的,于是Dog和Cat类分别重写了Animal类的抽象方法eat和sleep,然后用上了super关键字,直接调用了Animal的抽象方法。而Animal的work方法是已经写好的,子类对象要是要使用work方法,由于没有被重写,就会直接使用父类Animal的work方法,而eat和sleep已经被重写了,子类要是调用这俩个方法,就会直接使用自己重写过后的方法。
在类与对象的构造方法代码书写中,this指代的是类的对象,super指代是父类的对象。有继承关系的话,在构造子类关系之前,要先构造父类对象,所以说super要放在第一行。
接口类
定义:爪哇中的接口类是一种特殊的抽象类,它定义了一组方法的接口,但并不提供这些方法的实现。接口通常以interface来声明,一个类通过继承接口的方式,从而来继承接口的抽象方法。接口并不是类!
特点
- 抽象性:接口类是一种抽象类,它只定义方法的签名,而不提供实现。因此,实现接口的任何类都必须提供这些方法的具体实现。
- 纯虚函数:接口中的方法默认都是抽象方法,没有方法体,也就是所谓的“纯虚函数”。实现类必须实现接口中的所有方法。
- 可继承性:接口可以继承多个接口,这样就可以将多个接口的特性组合在一起。
- 多态性:通过实现接口,一个类可以实现多个接口,这样就可以使用同一个对象引用调用不同的接口方法,实现多态性。
- 无构造函数:接口不能有构造函数,因为它是一种抽象类型。
- 常用于定义行为:接口常用于定义一组相关的行为,可以被多个类实现。
例子:接口Animal,实例类Cat和Dog类
Animal接口
public interface Animal {
public void eat();
public void sleep();
public void work();
}
Cat类:实现了所有的Animal接口方法
public class Cat implements Animal {
@Override
public void eat() {
System.out.println("猫在吃东西");
}
@Override
public void sleep() {
System.out.println("猫在睡觉");
}
@Override
public void work() {
}
}
Dog类:没有实现所有的Animal接口,然后报错了!
public class Dog implements Animal {
@Override
public void eat() {
System.out.println("狗在吃东西");
}
@Override
public void sleep() {
System.out.println("狗在睡觉");
}
}
一样的,相较于抽象类,接口是不能实现方法的,不能有方法体。只能交给子类来实现,并且子类还必须要实现接口类的所有的方法。不然会报错,像是我们这里的dog类,由于少重写了一个方法,因此这里爆红了!(这里代码没有显示awa...)
类的多态
使用方式
- 必须在继承体系下。
- 子类必须要对父类中方法进行重写。
- 通过父类的引用调用重写的方法。
- 类捡对象可以是自己的对象 父类的对象可以建子类的对象 可以重写
定义格式:父类类型 变量名 = new 子类类型();
多个子类重写父类的方法后,在调用多态方式创建的父类对象方法时,会调用父类引用变量指向的子类中被重写的方法,这便是多态。
例子:父类Animal,子类Cat和子类Dog
父类Animal
public class Animal{
String name;
int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println("在吃");
}
public void sleep() {
System.out.println("在睡");
}
public void work() {
System.out.println("在工作");
}
}
子类Cat:重写eat和sleep方法
public class Cat extends Animal {
public Cat(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("猫在吃东西");
}
@Override
public void sleep() {
System.out.println("猫在睡觉");
}
}
子类Dog:重写eat和sleep方法
public class Dog extends Animal {
public Dog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("狗在吃东西");
}
@Override
public void sleep() {
System.out.println("狗在睡觉");
}
}
测试类:调用eat方法
Animal animal = new Animal("动物",11);//这是父类自己的对象
Animal dog = new Dog("狗狗",12);//父类多态子类dog
Animal cat = new Cat("猫猫",13);//父类多态子类cat
animal.eat();
dog.eat();
cat.eat();
//打印结果
在吃
狗在吃东西
猫在吃东西
通过例子,可以很明显的看到,多个子类重写父类的方法后,在调用多态方式创建的父类对象的方法时,会调用父类引用变量指向的子类中被重写的方法。
集合
爪哇的集合框架提供了一种强大且灵活的方式来存储和操作一组对象。相当于是一种更高级的数组,有更多的对‘数组’的编辑方式,但是存储的方式又与数组相差甚远。
主要集合接口和实现类
都是接口,不能直接创建自己的对象,但是可以创建子类的对象来实现。
- List:这是一个有序集合,可以包含重复元素。主要的实现类有 ArrayList 和 LinkedList。
- Set:这是一个不包含重复元素的集合。主要的实现类有 HashSet 和 TreeSet。
- Map:这是一个对象的映射,允许使用键来查找值。主要的实现类有 HashMap 和 TreeMap。
list集合:ArrayList数组
// 集合 List、set
// list 大小不固定 类型多样 =》 对象
List list = new ArrayList();
list.add(123);//添加了一个int类型的
list.add("123好");//添加了一个string类型的
System.out.println(list);
for (int i = 0; i < list.size(); i++) {//用for循环遍历打印
System.out.println(list.get(i));
}
for (Object obj: list) {//用foreach循环遍历打印
System.out.println(obj);
}
因为ArrayList比较常见,所以说我们举了Arraylist的例子,从图中可以看到,for循环遍历打印了数组,但是在foreach循环时,使用了Object的类型,这是因为ArrayList集合可以存储所有的数据类型。
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("123");
list.add("456");
但是ArrayList也可以指定集合内储存的数据类型。通过List<String> list = new ArrayList<>();的方式来指定,list集合就只能储存String类型的数据,存储其他类型的数据就会发生错误。ArrayList集合的底层就是一个数组,很方便进行增删操作,但是每次增删操作后,相当于都会进行新建数组、删除数组的一个操作。
//包装类
List<Integer> list1 = new ArrayList<>();
Integer integer = Integer.valueOf(123);//integer不能直接存储int类型的数
Integer b = 123; //自动装箱子
int bbb = b;
list集合常用指令
add(E e)
:将指定的元素添加到此列表的尾部。get(int index)
:返回在此列表中指定位置上的元素。remove(int index)
:移除此列表中指定位置上的元素。contains(Object o)
:返回此列表中包含指定元素的布尔值。size()
:返回此列表中的元素数。isEmpty()
:测试此列表是否为空。iterator()
:返回一个迭代器,该迭代器可以依次访问此列表中的元素。toArray()
:将此列表中的元素存储到数组中并返回该数组。
set集合
Set set = new HashSet<>();
set.add("b");
set.add("a");
set.add("c");
set.add(123);
set.add(456);
set.add(456);
System.out.println(set);
//打印结果
[a, b, c, 456, 123]
set集合不同于List集合的点是,它不允许重复的数据在集合中出现,并且也是相对无序的。我们往set集合中重复添加了两次456,但是集合中只出现了一个的456,说明集合是不允许重复数据存在,会自动消除数据的重复。可以看到我们添加数据的时候,先添加的字符串"b",再添加的字符串"a",先添加的数123,再添加的456,但是打印顺序的结果却是相反的,可见set集合是无序的。
set集合常用指令
add(E e)
:将指定的元素添加到此集合中。remove(Object o)
:从此集合中移除指定的元素(如果存在)。contains(Object o)
:返回指定元素在此集合中的存在性。size()
:返回此集合中的元素数。isEmpty()
:测试此集合是否为空。iterator()
:返回一个迭代器,该迭代器可以依次访问此集合中的元素。toArray()
:将此集合中的元素存储到数组中并返回该数组。
map集合
// 创建一个HashMap
Map<Integer,Object> map = new HashMap<>();
// 向map中添加元素
map.put(1, "1");
map.put(2, 2);
map.put(3, "3");
map.put(4, "4");
map.put(5, 5);
//直接打印map对象
System.out.println(map);
//打印结果
{1=1, 2=2, 3=3, 4=4, 5=5}
// foreach遍历Map的keySet()和values()
for (Integer key : map.keySet()) {
System.out.print(key + "——" + map.get(key) + "\t ");
}
//打印结果
1——1 2——2 3——3 4——4 5——5
//foreach values方法遍历Map
for (Object value : map.values()) {
System.out.print(value + "\t");
}
//打印结果
1 2 3 4 5
Map集合不同于其他两种集合的地方在于,它是采取键值对(Key——Value)的方式存储的,可以通过key找到对应value,value是可以重复的,但是key是唯一的。上图代码我们采取了2种方式遍历打印了map集合的各个元素,通过打印的结果我们可以很直观的看到,每个map的value值都是和key值一一对应的。在我看来,如果把map当成一个数组,那么key就是我们自定义的一个数组索引。
map集合常用指令
put(K key, V value)
:将指定的值与此映射中的指定键相关联(可选操作)。get(Object key)
:返回与此映射中的指定键关联的值。remove(Object key)
:移除此映射中指定键的关联,并返回其关联的值(如果存在)。containsKey(Object key)
:返回指定的键是否在此映射中具有关联值。containsValue(Object value)
:返回指定的值是否在此映射中具有关联键。size()
:返回此映射中的键/值对的数量。isEmpty()
:测试此映射是否为空。keySet()
:返回此映射中包含的键的集合视图。values()
:返回此映射中包含的值的集合视图。entrySet()
:返回此映射中包含的映射关系的集合视图。
异常处理
下面是一段在编译阶段不会报错的代码,但是在运行阶段会发生错误。
int result = 1 / 0;
//在爪哇中是不能进行除以0的操作的,会发生错误
//Exception in thread "main" java.lang.ArithmeticException: / by zero
在后续敲代码的很多时候,都会出现一些无法避免的报错。编译阶段的报错是可以避免的,因为编译要是不通过根本没办法运行代码awa;但是运行阶段的报错,这往往是我们难以预料的,但是我们可以对可能发生错误的代码段进行异常处理操作。
爪哇的异常处理是一种强大的错误处理机制,它允许我们在代码中识别和处理潜在的错误或异常情况。异常是程序在执行过程中出现的问题,例如尝试读取不存在的文件、除以零等。
try {
int result = 1 / 0;
System.out.println(result);//不会被打印,因为上句代码发生错误被try捕获了!
} catch (ArithmeticException e) {
System.out.println("发生除以零的错误");
} finally {
System.out.println("这个块总是被执行,无论是否发生异常");
}
System.out.println("结束");
//打印结果
//发生除以零的错误
//这个块总是被执行,无论是否发生异常
//结束
爪哇的异常处理分为3块,try、catch、finally,try代码块用于存放可能发生错误的代码块,try代码块中有语句发生错误的时候,try代码块内的后续代码将不会继续进行操作;而catch代码块用于对异常进行处理;finally代码块则是在所有代码执行完后,最后必定会执行的操作。在有的时候,finally代码是可以不存在的,但是try-catch好兄弟是一定要并肩战斗,有你有我,一鼓作气。
多线程
我们都知道,爪哇是一个自上而下执行的程序,一个程序只有一个进程,代码只能逐行执行。如果要写一个聊天软件,要能在传输文件的同时还能进行聊天,这个时候我们就要使用爪哇多线程的包,这样就可以让代码同时运行。
爪哇中的多线程是指同时执行多个线程,这些线程可以独立地执行不同的任务。通过使用多线程,可以同时执行多个操作,提高程序的执行效率。
进程和线程的关系
进程是:一个应用程序(1个进程是一个软件)。
线程是:一个进程中的执行场景/执行单元。
注意:一个进程可以启动多个线程。
线程之间的关系
线程A和线程B,堆内存 和 方法区 内存共享。但是 栈内存 独立,一个线程一个栈。每个栈和每个栈之间,互不干扰,各自执行各自的。
多线程的类方法
- 继承Thread
- 实现Runnable接口
- Timer定时器(不是多线程)
继承Thread类
Thread thread = new Thread() {
@Override
public void run() {
super.run();
//代码块
}
};
thread.start();//启动
Thread常用指令
Thread(Runnable target)
: 创建一个新的线程,将给定的目标作为其运行任务。start()
: 启动线程,执行其任务。join()
: 等待线程终止。interrupt()
: 中断线程(如果线程被阻塞或睡眠)。isAlive()
: 返回线程是否还在运行。getState()
: 返回线程的状态。setPriority(int priority)
: 设置线程的优先级。getPriority()
: 返回线程的优先级。setName(String name)
: 设置线程的名称。getName()
: 返回线程的名称。setDaemon(boolean on)
: 将线程设置为守护线程(如果为true)或用户线程(如果为false)。isDaemon()
: 返回线程是否为守护线程。
实现Runnable接口
Runnable runnable = new Runnable() {
@Override
public void run() {
//代码块
}
};
runnable.run();
Runnable常用指令
-
run()
: 这是要执行的任务的方法。在此方法中编写你希望线程执行的代码。
使用timer定时器
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
//代码块
}
},10,20);
timer常用指令
Timer()
: 创建一个新的计时器。schedule(TimerTask task, long delay)
: 安排指定的任务在给定的延迟后执行。schedule(TimerTask task, Date time)
: 安排指定的任务在给定的时间点执行。scheduleAtFixedRate(TimerTask task, long delay, long period)
: 安排指定的任务在给定的延迟后开始,并每隔指定的时间段执行一次。scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
: 安排指定的任务在给定的时间点开始,并每隔指定的时间段执行一次。
通过以上这些的方式,就可以创建我们的线程,周期性的让我们可以同时执行几个不同的操作。
结尾
由于时间关系,本文目前就又一次只能先更新到这里了。本文创作的初衷是为了应付老师布置的作业:"类与对象+部分扩展知识",说实话讲的都有些浅,并且有些内容咱都不太确定QAQ。如果文章有错误,请在本文下面评论或联系我,有看到的话会尽早修改,尽可能不误人子弟QAQ,虽然这可能和上一篇文章一样也是不会有人阅读的文章。联系方式:1749112929@qq.com.