Java学习笔记

一、基本格式

1.类

类是Java的基本结构,一个程序可以包含一个或多个类

类的申明:

/*
修饰符class类名{
程序代码
}
*/
public class HelloWorld{ //声明一个名为HelloWorld的类
}

2.修饰符

修饰符用于指定数据、方法、类的属性以及用法

修饰符示例:

public class HelloWorld{                  //public修饰为公有的
public static void main(String[]args){ //static修饰为静态的

}
}

3.块

Java中使用左大括号({)和右大括号(})将语句编组,组中的语句称为代码块或块语句

{
int i1 = 10;
int i2 = 20;
}

二、注释

1.单行注释

//xxxx

2.多行注释

/*
xxx
*/

注:多行注释中可以嵌套单行注释,但多行注释中不能嵌套多行注释

三、基本数据类型

1.整数类型

在Java中,整数类型分为字节型(byte)、短整型(short)、整型(int)和长整型(long)四种

在Java中直接给出一个整数值,其默认类型就是int类型。使用中通常有两种情况:

(1)直接将一个在byte在short类型取值范围内的整数值赋给byte或short变量,系统会自动把这个整数当成byte或short类型来处理。

(2)将一个超出int取值范围内的整数值赋给long变量,系统不会自动把这个整数值当成long类型来处理。此时必须声明long类型常量,即在整数值后面添加字母l或L。如果数值未超过int型的取值范围,则可以省略字母l和L。

long x = 99999;          //所赋的值未超出int取值范围,可以加L,也可省略
long z = 9999999999L; //所赋的值超出int取值范围,必须加L后缀

2.浮点数类型

在Java中,浮点数分为两种:单精度浮点数(float)和双精度浮点数(double)。

在Java中,使用浮点数数值时,默认的类型是double,在数值后面可加上d或D,作为double类型的标识。在数值后面加上f或F,则作为float类型的识别。若没有加上,Java就会将该数据视为double类型,而在编译时就会发生错误,提示可能会丢失精度。

double n = 10.0;    //数值默认为double型
float x = 10.0; //将丢失精度,错误赋值
float y =10.0f; //正确赋值,给数值添加f后缀,将数值视为float型

3.字符型

字符型变量用了存储单个字符,字符型值必须使用英文半角格式的单引号”‘“括起来。Java采用char表示字符型

char a = 'b';    //为一个char类型的变量赋值字符b

4.布尔类型

布尔类型的值只有true(真)和false(假)两种,Java的布尔类型用boolean表示,占用1B内存空间。

boolean b1 = true;   //声明boolean型变量值为true
boolean b2 = false; //声明boolean型变量值为false
boolean b3 = 1; //不能用非0来代表真,错误
boolean b4 = 0; //不能用0来代表假,错误

四、变量

1.声明变量

声明格式:

数据类型 变量名;
数据类型 变量名1,变量名2,......,变量名n;

2.变量的类型转换

隐式转换(自动类型转换):取值范围小的,和取值范围大的进行运算,小的会先提升为大的,再运算。byte、short、char三种类型的数据在运算的时候,都会直接提升为int,然后再进行运算

强制转换:如果把一个取值范围大的数值,赋值给取值范围小的变量,是不允许直接赋值的。如果一定要这么做,就需要加入强制转换。

格式:目标数据类型 变量名 = (目标数据类型)被强转的数据

注:强制转换可能出现错误

3.变量的作用域

变量声明在程序中的位置决定了变量的作用域。变量一定会声明在一对大括号中,该大括号所包含的代码区域就是这个变量的作用域。

4.常量

常量是不能被改变的数据。Java程序中使用的直接量称为常量,是在程序中通过源代码直接给出的值,在整个程序执行过程中都不会改变,也称最终量。

5.转义字符

  1. \b,退格键,Unicode码为\u0008
  2. \t,Tab键,Unicode码为\u0009
  3. \n,换行符,Unicode码为\u000A
  4. \r,回车符,Unicode码为\u000D

6.运算符

1.算术运算符
2.赋值运算符
3.关系运算符

&、&&运算符的区别:

都表示与操作,运算符前后的两个操作数皆为true,运算的结果才会为true,否则为flase。两者在使用上有一定的区别:使用&运算符,要求对运算符前后两个操作数都进行判断;而使用&&运算符,当运算符前面的操作数的值为false时,则其后面的操作数将不再判断。

|与||的区别同上。

4.位运算符

‘<<’ 左移 eg.0000 0001<<2 结果:0000 0100

‘>>’ 右移 eg.1000 0100>>2 结果:1110 0001

左移后右边空位补0,左边移出的舍弃;右移同理可得。

‘>>>’ 无符号右移运算符,将操作数的二进制位整体右移指定位数,右移后左边空位补0,右边移出去的舍弃。

进行位移运算遵循如下规则:

(1)对于低于int类型(byte、short和char)的操作数总是先自动转换为int类型后再位移。

(2)对于int类型的位移,当位移数大于int位数32时,Java先用位移数对32求余,得到的余数才是真正的位移数。

(3)对于long类型的位移,当位移数大于long位数64时,Java先用位移数对64求余,得到的余数才是真正的位移数。

但低于int类型的操作数进行无符号位移时,需注意,如果操作数是负数,在自动转换过程中会发生截断,数据丢失,导致位移结果不正确。

public class TestBitOperation{
public static void main(String[]args){
int n =-12>>>3; //对int型无符号右移3位
System.out.println(n);
byte a = -12;
byte b = 3;
byte m =(byte)(a>>>b); //对byte类型无符号右移3位
System.out.println(m);
}
}

五、程序的结构

一般来说,程序的结构分为顺序结构、选择结构和循环结构三种,它们都有一个共同的特点是都只有一个入口,也只有一个出口。

1.顺序结构

2.选择结构

Java提供了两种分支结构语句:if语句和switch语句。其中,if语句使用布尔表达式或布尔值作为分支条件来进行分支控制;而switch语句用于对多个整数值进行匹配,从而实现多分支控制。

1.if语句
if(布尔表达式){
程序代码
}
package com;

/**
* @author KK
* @date 2022/10/9
* @dec 描述
*/
public class TestIf {
public static void main(String[] args){
double PI = 3.14;
int r = -2;
double perimeter = 0.0;
double area = 0.0;
if(r>=0){
perimeter =2*r*PI;
}
System.out.println("圆半径"+r+"的周长为:"+perimeter);
r=6;
if (r>=0){
area=r*r*PI;
}
System.out.println("圆半径"+r+"的面积为:"+area);
}
}

2.if-else语句
3.三目运算符
判断条件?表达式1:表达式2

当判断条件成立时,执行表达式1,否则将执行表达式2.

4.if-else if-else语句

由于if语句体或else语句体可以是多条语句,所以如果需要在if-else里判断多个条件,可以“随意”嵌套。

if(布尔表达式1){
语句块1
}else if(布尔表达式2){
语句块2
}
......
else if(布尔表达式n){
语句块n
}else{
语句块n+1
}
5.switch语句
switch(表达式){
case 常量值1:
语句块1;
break;
case 常量值2:
语句块2;
break;
......
default :
默认语句块
}

使用switch语句需注意:

  1. switch语句的判定条件只能是byte、short、char和int四种基本类型,JDK5.0开始支持枚举类型,JDK7.0开始支持String类型,不能是boolean类型。
  2. 常量1~常量N必须与判断条件类型相同,且为常量表达式,不能是变量。
  3. case字句后面可以有多条语句,这些语句可以使用大括号括起来。
  4. 程序将从第一个匹配的case字句处开始执行后面的所有代码(包括后面case字句中的代码)。可以使用break跳出switch语句。
  5. default语句是可选的,当所有case字句条件都不满足时执行。

3.循环结构

Java程序设计中引入了循环语句。循环语句共有三种常见的形式:while循环语句、do-while循环语句和for循环语句

1.while循环
while循环语句也是条件判断语句,用于事先不知道循环次数的情况,其语法格式如下:
while(循环条件){
循环体
}
2.do-while循环
do{
循环体
}while(循环条件)

do-while语句与while语句还有一个明显的区别是,如果while语句误添加分号,会导致死循环,而do-while的循环条件后面必须有一个分号,用来表明循环结束。

package com;

/**
* @author KK
* @date 2022/10/11
* @dec 描述
*/
public class TestDoWhile {
public static void main(String[] args){
int sum = 0;
int i = 1;
do{
sum += i;
i++;
}while(i<=100);
System.out.println(i);
System.out.println("1~100的累加和:"+sum);
}
}

3.for循环
for(赋初始值;循环条件;迭代语句){
语句i;
...
语句n;
}
4.循环中断
1.break语句

注:如果break语句出现在嵌套循环中的内层循环,则break语句只会跳出当前层的循环。

public class TestBreak{
public static void main(String[] args){
for(int i = 0;i<10;i++){
System.out.println(i);
if(2==i)
break; //执行该语句将结束循环
}
}
}

在有些场景下,需要从很深的循环中退出时,可以使用带标记的break语句,标记必须break所在循环的外层循环之前定义才有意义,定义在当前循环之前,就失去标记的意义,因为break默认就是结束其所在循环。

package com;

/**
* @author KK
* @date 2022/10/11
* @dec 描述
*/
public class TestBreakLabel {
public static void main(String[] args){
label: //定义标记
for(int i =1;i<10;i++){
for(int j =1;j<10;j++){
System.out.println(i+","+j);
if(j%2==0)
break label; //跳出label标记所标识的循环
}
}
}

}
2.continue语句

在while、do-while和for语句的循环体中,执行continue语句可以结束本次循环而立即测试循环体的条件,执行下一次循环,但不会终止整个循环

六、数组

在一个数组中,数组元素的类型是唯一的,即一个数组中只能存储同一种数据类型的数据,而不能存储多种数据类型的数据。

定义方式1
数据类型[]数组名;
定义方式2
数据类型 数组名[];

1.数组的初始化

数组初始化,就是让数组名指向数组对象的过程,该过程主要分为两个步骤:一是对数组名进行初始化,即为数组中的元素分配内存空间和赋值;二是对数组名进行初始化,即将数组名赋值为数组对象的引用。

1.静态初始化
方式1
Int[]array; //声明一个int类型的数组
array = new int[]{1,2,3,4,5}; //静态初始化数组
int[] array = new int[]{1,2,3,4,5}; //声明并初始化数组
方式2
Int[] array = new int[]{1,2,3,4,5}; //声明并初始化一个int类型的数组

其中大括号包含数组元素值,元素值之间用逗号”,”分隔。

2.动态初始化

动态初始化是指程序员在初始化数组时指定数组的长度,由系统为数组元素分配初始值。

数组动态初始化的具体示例如下:

int[] array = new int[10];    //动态初始化数组

2.数组的常用操作

1.访问数组

在Java中,数组对象有一个length属性,用于表示数组的长度

获取数组长度的语法格式如下:

数组名.length

接下来用length属性获取数组的长度,具体示例如下:

int[] list =new int[10];      //定义一个int类型的数组
int size = list.length; //size = 10;数组的长度
2.数组元素的存取

通过操作数组的下标可以访问到数组中的元素,也可以实现数组元素的存取。

package com;

/**
* @author KK
* @date 2022/10/11
* @dec 描述
*/
public class TestArray {
public static void main(String[] args){
//声明数组
int[] a =new int[5];
//存入数组元素
a[0]=5;
a[1]=10;
a[4]=9;
//读取数组元素
System.out.print("数组的元素为:");
System.out.println(a[0]+","+a[1]+","+a[3]+","+a[4]);
}
}
//从输出结果来看,数组的元素已经存取成功,但数组下标为2、3的位置中并未存入数据,但是却能取到数据为0的元素,可见声明为int类型的数组元素的默认值为0
3.数组遍历

for循环遍历

public class TestArrayTraversal{
public static void main(String[] args ){
int[] list={1,2,3,4,5}; //定义数组
for(int i = 0;i<list.length;i++) { //遍历数组元素
System.out.println(list[i]); //索引访问数组
}
}
}
4.数组最大值和最小值
5.数组排序——冒泡排序法
package com;

/**
* @author KK
* @date 2022/10/11
* @dec 描述
*/
public class TestBubbleSort {
public static void main(String[] args){
int[] array = {88,62,12,100,28};
//外层循环控制排序轮数
//最后一个元素,不用再比较
for (int i=0;i<array.length-1;i++){
//内层循环控制元素两两比较的次数
//每轮循环沉底一个元素,沉底元素不用再参加比较
for (int j=0;j<array.length-1-i;j++){
//比较相邻元素
if (array[j]>array[j+1]){
//交换元素
int tmp=array[j];
array[j]=array[j+1];
array[j+1]=tmp;
}
}
//打印每轮排序结果
System.out.print("第"+(i+1)+"轮排序:");
for (int j =0;j<array.length;j++){
System.out.print(array[j]+"\t");
}
System.out.println();
}
System.out.print("最终排序: ");
for (int i=0;i<array.length;i++){
System.out.print(array[i]+"\t");
}
System.out.println();
}
}

3.二维数组

二维数组的声明

int[][]array;
int array[][];

二维数组的动态初始化示例:

array = new int[3][2];      //动态初始化3×2的二维数组
array[0]={1,2}; //初始化二维数组的第一个元素
array[1]={3,4}; //初始化二维数组的第二个元素
array[2]={5,6}; //初始化二维数组的第三个元素

二维数组的静态初始化示例:

array=new int[][]{
{1},
{2,3},
{4}
};

4.锯齿数组

二维数组中每一行就是一个一维数组,因此,各行的长度就可以不同,这样的数组称为锯齿数组。

七、方法

方法是一段可重复使用的代码,为执行一个操作组合在一起的语句集合,用于解决特定的问题。

1.方法的定义

修饰符 返回值类型 方法名([参数类型 参数名1,参数类型 参数名2,...]){
方法体
return 返回值;
}

定义方法需注意:

(1)修饰符:方法的修饰符较多,有对访问去权限进行限定的,有静态修饰符static,还有最终修饰符final等。

(2)返回值类型:限定返回值的类型。

(3)参数类型:限定调用方法时传入参数的数据类型。

(4)参数名:是一个变量,用于接受调用方法指定类型的值。

(5)return:关键字,用于结束方法以及返回方法指定类型的值。

(6)返回值:被return返回的值,该值返回给调用者。

方法头中声明的变量称为实际参数,简称实参。形参列表是指形参的类型、顺序和数量。方法不需要任何参数,则形参列表为空。

2.方法的调用

int large = max(3,4);    //将方法的返回值赋给变量
System.out.println(max(3,4)); //直接打印方法的返回值
System.out.println("Hello World!"); //println方法没有返回值

如果方法定义中包含形参,调用时必须提供实参。实参的类型必须与形参的类型兼容,实参顺序必须与形参的顺序一致。实参的值传递给方法的形参,称为值传递,方法内部对形参的修改不影响实参值。

package com;

/**
* @author KK
* @date 2022/10/13
* @dec 描述
*/
public class TestCallMethod {
public static void main(String[] args){
int n = 5;
int m = 2;
System.out.println("before main\t:n="+n+",m="+m);
swap(n,m);
System.out.println("end main\t:n="+n+",m="+m);
}
//交换两个数
public static void swap(int n,int m){
System.out.println("before swap\t:n="+n+",m="+m);
int tmp=n;
n=m;
m=tmp;
System.out.println("end swap\t:n="+n+",m="+m);
}
}
/*结果
before main :n=5,m=2
before swap :n=5,m=2
end swap :n=2,m=5
end main :n=5,m=2
说明swap不能交换实参的值
*/

3.方法的重载

方法重载是指方法名称相同,但形参列表不同的方法。调用重载的方法时,Java编译器会根据实参列表寻找最匹配的方法进行调用。

package com;

import java.util.function.DoublePredicate;

/**
* @author KK
* @date 2022/10/13
* @dec 描述
*/
public class TestOverload {
public static void main(String[] args){
//调用max(int,int)方法
System.out.println("3和8的最大值:"+max(3,8));
//调用max(double,double)方法
System.out.println("3.0和8.0的最大值:"+max(3.0,8.0));
//调用max(double,double,double)方法
System.out.println("3.0、5.0和8.0的最大值:"+max(3.0,5.0,8.0));
//调用max(double,double)方法
System.out.println("3和8.0的最大值:"+max(3,8.0));
}
//返回两个整数的最大值
public static int max(int num1,int num2){
int result;
if(num1>num2)
result = num1;
else
result = num2;
return result;
}
//返回两个浮点数的最大值
public static double max(double num1,double num2){
double result;
if(num1>num2)
result = num1;
else
result = num2;
return result;
}
//返回三个浮点数的最大值
public static double max(double num1,double num2,double num3){
return max(max(num1,num2),num3);
}
}

但值得一提的是,调用一个方法时,出现两个或多个可能的匹配时,编译器无法判断哪个是最精确的匹配,则会产生编译错误,称为歧义调用。

4.方法的递归

递归用于解决使用简单循环难以实现的问题。

package com;

/**
* @author KK
* @date 2022/10/13
* @dec 描述
*/
public class TestRecursion {
public static void main(String[] args){
System.out.println("4的阶乘:"+fact(4));
}
/*
计算阶乘
阶乘计算公式:
0!=1
n!=n*(n-1)!;n>0
*/
public static long fact(int n){
//结束条件
if(n==0)
return 1;
return n*fact(n-1);
}
}

八、面向对象

1.面向对象的概念

面向对象思想是人类最自然的一种思考方式,它将所有预处理的问题抽象为对象,同时了解这些对象具有哪些相应的属性以及如何展示这些对象的行为,以解决这些对象面临的一些实际问题,这样就在程序开发中引入了面向对象设计的概念,面向对象设计实际上就是对现实世界进行建模工作。

1.封装

封装是面向对象程序设计的核心思想。它是指将对象的属性和行为封装起来,其载体就是类,类通常对客户隐藏其实现细节,这就是封装的思想。

eg.计算机的主机是由内存条、硬盘、风扇等部件组成,生产厂家把这些部件用一个外壳封装起来组成主机,用户在使用该主机时,无须关心其内部的组成及工作原理。

2.继承

继承是面向对象程序设计提高重用性的重要措施。它体现了特殊类与一般类之间的关系,当特殊类包含一般类的所有属性和行为,并且特殊类还可以有自己的属性和行为时,称作特殊类继承了一般类。一般类又称为父类或基类,特殊类又称为子类或派生类。

eg.已经描述了汽车模型这个类的属性和行为,如果需要描述一个小轿车类,只需让小轿车类继承汽车模型类,然后再描述小轿车类特有的属性和行为,而不必再重复描述一些在汽车模型类中已有的属性和行为。

3.多态

多态是面向对象程序设计的重要特征。

eg.生活中也有很多多态,如学校的下课铃声响后,有学生去买零食、有学生去打球、有学生在聊天等等。

2.类与对象

对象是事物存在的实体,如学生、汽车等。在计算机世界中,面向对象程序设计的思想要以对象来思考问题,首先要将现实世界的实体抽象为对象,然后考虑这个对象具备的属性和行为。

1.类的定义
class 类名{
属性类型 成员变量名; //成员变量(对象属性)
...
修饰符 返回值类型 方法名([参数列表]){ //成员方法(对象行为)
//方法体
return 返回值;
}
}

eg.

class Person{
String name;
int age;
public void say(){
System.out.println("姓名:"+name+",年龄:"+age);
}
}
2.对象的创建与使用

声明对象格式:

类名 对象名

初始化类,Java使用new关键字来创建对象,也称为实例化对象,其语法格式如下:

对象名 = new 类名();

or

类名 对象名 = new 类名();

eg.

Person p = new Person();

访问对象的成员变量和成员方法:

对象名.成员变量;
对象名.成员方法();
package LearnJava;

/**
* @author KK
* @date 2022/10/14
* @dec 描述
*/
class Person{
String name;
int age;
public void say(){
System.out.println("姓名:"+name+",年龄:"+age);
}
}
public class TestPersonDemo {
public static void main(String[]args){
Person p1=new Person(); //实例化第一个Person对象
Person p2=new Person(); //实例化第二个Person对象
p1.name="张三"; //为name属性赋值
p1.age=18; //调用对象的方法
p1.say();
p2.say();
}
}

另外,需要注意的是,一个对象能被多个变量所引用,当对象不被任何变量所引用时,该对象就会成为垃圾,不能再被使用。

package LearnJava;

/**
* @author KK
* @date 2022/10/14
* @dec 描述
*/
class Person1{
String name;
int age;
public void say(){
System.out.println("姓名:"+name+",年龄"+age);
}
}
public class TestObjectRef {
public static void main(String[] args){
Person1 p1 = new Person1();
Person1 p2 = new Person1();
p1.name="张三";
p1.age=18;
p2.name="李四";
p2.age=28;
p2=p1;
p1.say();
p2.say();
}
}
/*
结果:
姓名:张三,年龄18
姓名:张三,年龄18
*/
/*
p2被赋值p1后,会断开原有引用的对象,而和p1引用同一对象。因此打印以上内容。此时,p2原有引用的对象不再被任何变量所引用,就成了垃圾对象,不能再被使用,只等待垃圾回收机制进行回收。
*/
3.类的封装
class Person{
String name; //声明姓名属性
int age; //声明年龄属性
public void say{ //定义显示信息的方法
System.out.println("姓名:"+name+",年龄:"+age);
}
}
public class TestPersonDemo01{
public static void main(String[] args){
Person p1 = new Person(); //实例化一个Person对象
p1.name = "张三"; //为name属性赋值
p1.age = -18; //为age属性赋值
p1.say(); //调用对象的方法
}
}

为了避免外界随意访问类中的属性,就需要用到封装,即不让使用者访问类的内部成员。Java中使用private关键字来修饰私有属性,私有属性只能在它所在的类中被访问。但这样做使所有的对象都不能访问这个类中的私有属性。为了让外部使用者访问类中的私有属性,需要提供public关键字修饰的属性访问器,即用于设置属性的setXxx()方法和获取属性的getXxx()方法。

package LearnJava;

/**
* @author KK
* @date 2022/10/14
* @dec 描述
*/
class Person2{
private String name; //声明姓名私有属性
private int age; //声明年龄私有属性
public void setName(String str){ //设置属性方法
name = str;
}
public String getName(){ //获取属性方法
return name;
}
public void setAge(int n){
if(n>0&&n<200) //验证年龄,过滤掉不合理的
age = n;
}
public int getAge(){
return age;
}
public void say() { //定义显示信息的方法
System.out.println("姓名:" + name + ",年龄:" + age);
}
}
public class TestPersonDemo03 {
public static void main(String[] args){
Person2 p1 = new Person2(); //实例化一个Person对象
p1.setName("张三"); //为name属性赋值
p1.setAge(-18); //为age属性赋值
p1.say(); //调用对象的方法
}
}
4.访问修饰符

Java中的访问修饰符是指能够控制类、成员变量、方法的使用权限的关键字。通常放在语句的最前端。类的访问修饰符只有一个public,属性和方法能够被四个修饰符修饰,分别是:public、private、protected,还有一个默认权限default。

1.公有访问控制符(public)

对所有类可见,被声明public的类、方法和接口允许被程序中的任何类访问。Java的类是通过包的概念来组织的。包是类的一个松散的集合,处于同一个包中的类可以不需要任何说明方便地相互访问和引用,而对于不同包中的类,则需要导入相应public类所在的包。每个java程序的主类必须是public修饰的类,否则Java解释器将不能运行该类。

2.私有访问控制符(private)

私有的,即在同一类内可见。被private修饰的属性或方法被提供了最高的保护级别,只能由该类自身访问或修改,而且不能被任何其他类(包括该类的子类)来获取或引用

3.保护防护控制符(protected)

受保护的,即对同一包的类和所有子类可见,可以用来修饰属性、方法,不能修饰类。protected修饰的成员变量可以被3种类所引用:该类自身、与它在同一个包中的其他类、在其他包中该类的子类。使用protected修饰符的主要作用是允许其他包中该类的子类来访问父类的特定属性。

4.默认访问控制符(defaulted)

默认访问控制权规定,该类只能被同一个包中的类访问和引用,而不可以被其他包中的类使用,这种访问特性又称为包访问性。

5.构造方法
1.构造方法的定义

构造方法是使用new关键字创建一个时被调用的,构造方法时需注意:

(1)构造方法名与类名相同。

(2)构造方法没有返回值类型。

(3)构造方法中不能使用return返回一个值。

package LearnJava;

/**
* @author KK
* @date 2022/10/17
* @dec 描述
*/
class Person0{
public Person0(){
System.out.println("构造方法自动被调用");
}
}
public class TestPersonDemo04 {
public static void main(String[] args){
System.out.println("声明对象:Person p = null");
Person0 p = null; //声明对象时不调用构造方法
System.out.println("实例化对象:p=new Person0()");
p= new Person0();
}
}
2.构造方法的重载

与普通方法一样,只要每个构造方法的参数列表不同,即可实现重载。

3.this关键字

类在定义成员方法时,局部变量和成员变量可以重名,但此时不能访问成员变量。为避免这种情形,Java提供了this关键字,表示当前对象,指向调用的对象本身。

class Person{
public void equals(Person p){
System.out.println(this); //打印this的地址
System.out.println(p); //打印对象地址
if(this==p) //判断当前对象与this是否相等
System.out.println("相等");
else
System.out.println("不相等");
}
}
public class TestThis{
public static void main(String[] args){
Person p1 = new Person();
Person p2 = new Person();
p1.equals(p1);
p2.equals(p2);
}
}

this关键字在程序中主要有三种用法,下面分别讲解各种用法。

1.使用this调用类中的属性

this关键字可以明确调用类的成员变量,不会与局部变量名发生冲突。

package LearnJava;

/**
* @author KK
* @date 2022/10/14
* @dec 描述
*/
class Person3 {
private String name; //声明姓名私有属性
private int age; //声明年龄私有属性
public Person3(String name, int age) {
this.name = name;
this.age = age;
}

public void say() {
System.out.println("姓名:" + this.name + ",年龄:" + this.age);
}
}
public class TestThisRefAttr{
public static void main(String[] args){
Person3 p = new Person3("张三",18);
p.say();

}
}
//构造方法的形参与成员变量同名,使用this明确调用成员变量,避免了与局部变量产生冲突。
2.使用this调用成员方法

this既然可以访问成员变量,那么也可以访问成员方法

class Person {
private String name; //声明姓名私有属性
private int age; //声明年龄私有属性
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void say() {
System.out.println("姓名:" + this.name + ",年龄:" + this.age);
this.log("Person.say"); // 明确使用this调用log()成员方法
}
}
public class TestThisRefFun{
public static void main(String[] args){
Person p = new Person("张三",18);
p.say();

}
}
3.使用this调用构造方法

构造方法是在实例化时被自动调用的,因此不能直接像调用成员方法一样去调用构造方法,但可以使用this([实参列表])的方式调用其他的构造方法

package LearnJava;

/**
* @author KK
* @date 2022/10/17
* @dec 描述
*/
class Person5{
private String name; //声明姓名私有属性
private int age; //声明年龄私有属性
public Person5(){ //这里的Person5()是构造方法
System.out.println("调用无参构造方法");
}
public Person5(String name,int age){
this(); //此时调用的是没有返回值的Person5类
System.out.println("调用有参构造函数");
this.name = name; //明确表示为类中的name属性赋值
this.age = age; //明确表示为类中的age属性赋值
}
public void say(){
System.out.println("姓名:"+this.name+",年龄:"+this.age);
}
}
public class TestThisRefConstructor{
public static void main(String[] args){
Person5 p = new Person5("张三",18); //调用Person5 含有两个参数的方法
p.say();
}
}

另外,在使用this调用构造方法时,还需注意:在构造方法中,使用this调用构造方法的语句必须位于首行,且只能出现一次。

4.static关键字

1.静态变量

使用static修饰的成员变量,称为静态变量或类变量,它被类的所有对象共享,属于整个类所有,因此可以通过类名直接访问。而未使用static修饰的成员变量称为实例变量,它属于具体对象独有,只能通过引用变量访问。

class Person{
int count; //保存对象创建的个数
public Person(){
count++;
}
}
public class TestInstanceVariable{
public static void main(String[] args){
//创建Person对象
Person p1 = new Person();
Person p2 = new Person();
Person p3 = new Person();
Person p4 = new Person();
Person p5 = new Person();
System.out.println(p5.count);
}
}
/*输出为1
由于实例变量count是属于类的对象的,对象之间的count是不相关的,它们被存储在不同的内存位置。因此程序运行结果输出的1是引用对象count值。要想实现用count记录类对象被创建的次数,可使用static关键字来修饰成员变量即可达到目的
将第2行代码更换成 static int count;即可实现最后输出5
*/

注:static关键字在修饰变量的时候只能修饰成员变量,不能修饰方法中的局部变量。

2.静态方法

使用static修饰的成员方法,称为静态方法,无须创建类的实例就可以调用静态方法,静态方法可以通过类名调用。

静态方法只能访问类的静态成员(静态变量、静态方法),不能访问类中的实例成员(实例变量和实例方法)。这是因为未被static修饰的成员都是属于对象的,所以需要先创建对象才能访问,而静态方法在被调用时可以不创建任何对象。

3.代码块

代码块是指用”{}”括起来的一段代码,根据位置和声明关键字不同,代码块分为普通、构造、静态、同步四种代码块。

普通代码块就是在方法名或方法体内用大括号”{}”括起来的一段代码。

构造代码块就是直接定义在类中的代码块,它没有任何前缀、后缀及关键字修饰。创建对象时,构造方法被自动调用,构造代码块也是在创建对象时被调用,但它在创造方法之前被调用,因此,构造代码块也可以用来初始化成员变量。另外,构造代码块会优先于构造方法执行。

package LearnJava;
class Constructor{
public Constructor(){ //定义构造方法
System.out.println("构造方法");
}
{ //定义构造代码块
System.out.println("构造代码块");
}
}
public class TestConstructorCodeBlock{
public static void main(String[] args){
//实例化对象
new Constructor();
new Constructor();
new Constructor();
}
}

静态代码块就是使用static关键字修饰的代码块,它是最早执行的代码块

package LearnJava;

/**
* @author KK
* @date 2022/10/24
* @dec 描述
*/
class StaticCodeBlock{
public StaticCodeBlock(){ //定义构造方法
System.out.println("构造方法");
}
{ //定义构造代码块
System.out.println("构造代码块");
}
static{ //定义静态代码块
System.out.println("静态代码块");
}
}
public class TestStaticCodeBlock {
public static void main(String[] args){
//实例化对象
new StaticCodeBlock();
new StaticCodeBlock();
new StaticCodeBlock();
}
}

5.内部类

1.成员内部类

成员内部类是指类作为外部类的一个成员,能直接访问外部类的所有成员,但在外部类中访问内部类,则需要在外部类中创建内部类的对象,使用内部类的对象来访问内部类中的成员。同时,若要在外部类外访问内部类,则需要通过外部类对象去创建内部类对象,在外部类创建一个内部类对象的语法格式如下:

外部类名.内部类名 引用变量名 = new 外部类名().new 内部类名()
package LearnJava;

/**
* @author KK
* @date 2022/10/24
* @dec 描述
*/
class Other{
//定义类成员
private String name="Other";
private int count;
//定义内部类
class Inner{
private String name = "Other.Inner";
public void say(){
//内部类成员方法中访问外部类私有成员变量
//Other.this表示外部类对象
System.out.println(Other.this.name);
System.out.println(":"+count);
}
}
}
public class TestInnerClass {
public static void main(String[]args){
//创建内部类对象
Other.Inner obj = new Other().new Inner();
obj.say();
}
}
2.静态内部类

static关键字修饰的内部类称为静态内部类。静态内部类可以有实例成员和静态成员,它可以直接访问外部类的静态成员,但如果想访问外部类的实例成员,就必须通过外部类的对象去访问。另外,如果在外部类外访问静态内部类成员,则不需要创建外部类对象,只需创建内部类对象即可。

创建内部类对象:

外部类名.内部类名 引用变量名 = new 外部类名.内部类名()
3.方法内部类

在成员方法中定义的类,它与局部变量类似,作用域为定义它的代码块,因此它只能在定义该内部类的方法内实例化,不可以在此方法外对其实例化。

4.匿名内部类

匿名内部类就是没有名称的内部类。创建匿名内部类时会创建一个该类的对象,该类定义立即消失,匿名内部类不能重复使用。

6.类的继承

1.用法
class 子类名 extends 父类名{
属性和方法
}

Java使用extends关键字指明两个类之间的继承关系。子类继承了父类中的属性和方法,也可以添加新的属性和方法。

package LearnJava;

/**
* @author KK
* @date 2022/11/4
* @dec 描述
*/
class Parent{
String name;
double property;
public void say(){
System.out.println(name+"的年龄"+property);
}
}
//定义子类继承父类
class Child extends Parent{
int age;
public void sayAge(){
System.out.println(name+"的年龄:"+age);
}
}
public class TestExtends {
public static void main(String[]args){
//创建Child对象
Child c =new Child();
//Child对象本身没有name成员变量
//因为Child父类有name成员变量,所以Child继承了父类的成员变量和方法
c.name="小明";
c.property=100;
c.age=20;
c.say();
c.sayAge();
}
}

Child类通过extends关键字继承了Parent类,继承了其所有的成员,且可以扩展功能。

但Java语言只支持单继承,不允许多重继承,即一个子类只能继承一个父类,否则会引发编译错误。

但它支持多层继承,即一个类的父类可以继承另外的父类。因此,java类可以有无限多个间接父类。

eg.

class A{}
class B extends A{}
class C extends B{}
2.重写父类方法

在进行方法重写时必须考虑权限,即被子类重写的方法不能拥有比父类方法更加严格的访问权限。

package LearnJava;

/**
* @author KK
* @date 2022/11/4
* @dec 描述
*/
class Parent1{
protected void say(){
System.out.println("父辈");
}
}
//定义子类继承父类
class Child1 extends Parent1{
public void say(){
System.out.println("子女");
}
}
public class TestOverride {
public static void main(String[]args){
//闯进Child对象
Child1 c =new Child1();
c.say();
}
}
//输出结果:子女 说明重写成功

注:

(1)方法的重载是在同一个类中,方法的重写是在子类与父类中。

(2)方法重载要求:方法名相同,参数个数或参数类型不同。

(3)方法重写要求:子类与父类的方法名、返回值类型和参数列表相同。

3.super关键字
super.成员变量
super.成员方法([实参列表])

super可以实现调用父类被重写的成员方法,可以在子类中访问被隐藏的父类成员。

子类继承父类时,并没有继承父类的构造方法,但子类构造方法可以调用父类的构造方法。在一个构造方法中调用另一个重组在的构造方法时应使用this关键字,在子类构造方法中调用父类的构造方法时应使用super关键字。

super([参数列表])
package LearnJava;

/**
* @author KK
* @date 2022/11/4
* @dec 描述
*/
//定义父类
class Parent2{
String name;
public Parent2(String name){
this.name= name;
}
public void say(){
System.out.println("父辈");
}
}
class Child2 extends Parent2{
public Child2(){
super("Parent");
}
public void say(){
System.out.println("姓名:"+name);
}
}
public class TestSuperRefConstructor {
public static void main(String[] args){
//创建Child对象
Child2 c = new Child2();
c.say();
}
}

另外,如果子类中没有显式地调用父类的构造方法,那么将自动调用父类中不带参数的构造方法。

4.final关键字

在Java中,为了考虑安全因素,要求某些类不允许被继承或者不允许被子类修改,这时可以用final关键字修饰。

(1)final修饰的类不能被继承

(2)final修饰的方法不能被子类重写。

(3)final修饰的变量是常量,初始化后不能再修改。

使用final关键字修饰的类称为最终类,表示不能再被其他类继承,如Java中的String类

使用final关键词修饰的方法,称为最终方法,表示子类不能再重写此方法

使用final关键词修饰的变量,称为常量,只能赋值一次。再次对该变量进行赋值时,程序在编译时会报错。

7.抽象类和接口

1.抽象类

Java中可以定义不含方法体的方法,方法的方法体由该类的子类根据实际需求去实现,这样的方法称为抽象方法,包括抽象方法的类必须是抽象类。

Java中提供了abstract关键字,表示抽象的意思,用abstract修饰的方法,称为抽象方法,是一个不完整的方法,只有方法的声明,没有方法体。用abstract修饰的类,称为抽象类。抽象类可以不包含任何抽象方法。

//用abstract修饰抽象类
abstract class Parent{
//abstract修饰抽象方法,只有声明,没有实现
public abstract void say();
}

抽象类不能被实例化,即不能用new关键字创建对象。因此,必须通过子类继承抽象去实现抽象方法。

//用abstract修饰抽象类
abstract class Parent5 {
//abstract修饰抽象方法,只有声明,没有实现
public abstract void say();
}
//继承抽象类
class Child5 extends Parent5 {
//实现抽象方法
public void say () {
System.out.println("Child");
}
}
public class TestAbstractClass{
public static void main(String[]args){
Child5 c =new Child5();
c.say();
}
}

需要注意的是,具体子类必须实现抽象父类中的所有抽象方法,否则子类必须要声明为抽象类

另外,抽象方法不能用static来修饰,因为static修饰的方法可以通过类名调用,调用将调用一个没有方法体的方法,肯定会出错;抽象方法也不能用final关键字修饰,因为被final关键字修饰的方法不能被重写,而抽象方法的实现需要在子类中实现;抽象方法也饿不能用private关键字修饰,因为子类不能访问带private关键字的抽象方法。

抽象类中可以定义构造方法,因为抽象类仍然使用的是类继承关系,而且抽象类也可以定义成员变量。因此,子类在实例化时必须先对抽象类进行实例化。

2.接口

接口是全局常量和公共抽象方法的集合,可被看作一种特殊的类,也属于引用类型。每个接口都被编译成独立的字节码文件。Java提供interface关键字,用于声明接口

interface接口名{
全局常量声明
抽象方法声明
}

接下来演示interface关键字的作用

//用interface声明接口
interface Parent{
String name; //等价于public static final String name;
void say(); //等价于public abstract void say();
}

接口中定义的变量和方法都包含默认的修饰符,其中定义的变量默认声明为”public static final”,即全局常量。另外,定义的方法默认声明为”public abstract”,即抽象方法。

1.接口的实现
class 类名 implements 接口列表{
属性和方法
}
interface Person{
void say();
}
interface Parent{
void work();
}
//用implements实现两个接口
class Child implements Person,Parent{
public void work(){
System.out.println("学习");
}
public void say(){
System.out.println("Child");
}
}
public class TestImplements {
public static void main(String[]args){
Child c = new Child();
c.say();
c.work();
}
}
2.接口的继承
interface 接口名 extends 接口列表{
全局常量声明
抽象方法声明
}
//用interface声明接口
interface Person{
void say();
}
//用extends继承接口
interface Parent extends Person{
void work();
}
//用implements实现两个接口
class Child implements Parent{
public void work(){
System.out.println("学习");
}
public void say(){
System.out.println("Child");
}
public class TestInterfaceExtend{
public static void main(String[]args){
Child c=new Child();
c.say();
c.work();
}
}
}

8.多态

多态是指同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在Java中指把类中具有相似功能的不同方法使用同一个方法名实现,从而可以使用相同的方式来调用这些具有不同功能的同名方法。

package LearnJava2;

/**
* @author KK
* @date 2022/11/7
* @dec 描述
*/
//定义Person类
class Person2{
public void say(){
System.out.println("Person");
}
}
//定义Parent类继承Person类
class Parent2 extends Person2{
public void say(){
System.out.println("Parent");
}
}
//定义Child类实现Parent类
class Child2 extends Parent2{
public void say(){
System.out.println("Child");
}
}
public class TestPolymorphism {
public static void main(String[]args){
//定义Person类型引用变量
Person2 p =null;
//使用Person类型变量引用Parent对象
p=new Parent2();
p.say();
//使用Person类型变量引用Child对象
p=new Child2();
p.say();
//使用Parent类型变量引用Child对象
Parent2 p2=new Child2();
p2.say();
}
}

动态绑定机制原理是:当调用实例方法时,Java虚拟机从该变量的实际类型开始,沿着继承链向上查找该方法的实现,直到找到为止,并调用首次找到的实现。

对象的类型转换是指可以将一个对象的类型转换成继承结构中的另一种类型。类型转换分两种:

(1)向上转型,是从子类到父类的转换,也称隐式转换。

(2)向下转型,是从父类到子类的转换,也称显示转换。

class Person{
public void say(){
System.out.println("Person");
}
}
//定义Parent类继承Person类
class Parent extends Person{
public void say(){
System.out.println("Parent");
}
}
//定义Child类继承Parent类
class Child extends Parent{
public void say(){
System.out.println("Child");
}
}
public class TestTypeCast{
public static void main(String[]args){
Person p = new Child(); //向上转型
Parent o = (Parent)p; //向下转型
o.say();
}
}

9.Object类

Java中提供一个Object类,是所有类的父亲。如果一个类没有显式地指定继承类,则该类的父类默认为Object。

class ClassName{}
class ClassName extends Object{}

在Object类中提供了很多方法

1.toString()方法

调用一个对象的toString()方法会默认返回一个描述该对象的字符串,它由该对象的所属类名、@和对象十六进制形式的内存地址组成

class Person{
private S
}
2.equals()方法

equals()方法是用于测试两个对象是否相等

equals()方法与直接使用==运算符检测两个对象结果相同,比较的是地址

10.工厂设计模式

1.简单工厂模式

简单工厂模式又称静态工厂方法,它的核心是类中包含一个静态方法,该方法用于根据参数来决定返回实现同一接口不同类的实例

package LearnJava2;

/**
* @author KK
* @date 2022/11/18
* @dec 描述
*/
//定义产品接口
interface Product{
}
//定义安卓手机类
class Android implements Product{
public Android(){
System.out.println("安卓手机被创建!");
}
}
//定义苹果手机类
class Iphone implements Product{
public Iphone(){
System.out.println("苹果手机被创建!");
}
}
//定义工厂类
class SimpleFactory{
public static Product factory(String className){
if("Android".equals(className)){
return new Android();
}else if("Iphone".equals(className)){
return new Iphone();
}else{
return null;
}
}
}
public class TestSimpleFactoryPattern {
public static void main(String[]args){
//根据不同的参数生成产品
SimpleFactory.factory("Android");
SimpleFactory.factory("Iphone");
}
}
2.工厂方法模式

工厂方法模式为工厂类定义了接口,用多态来削弱了工厂类的职责

3.抽象工厂模式

抽象工厂模式用于意在创建一系列互相关联或互相依赖的对象

11.包

1.包的定义与使用
package 包名

包是Java提供的一种区别类的名字空间的机制,是类的组织方式,是一组相关类和接口的集合,它提供了访问权限和命名的管理机制。

需注意:

  • 包名中字母一般都要小写。
  • 包的命名规则:将公司域名反转作为包名
  • package语句必须是程序代码中的第一行可执行代码
  • package语句最多只有一句

包与文件目录类似,可以分成多级,多级之间用”.”符号进行分隔

package com.1000phone.www;

如果在程序中已声明了包,就必须将编译生成的字节码文件保存到与包名同名的子目录中,可以使用带包编译命令。

javac -d .Source.java

其中”-d”表示生成以package定义为准的目录,”.”表示在当前所在的文件夹中生成。编译器会自动在当前目录下建立与包名同名的子目录,并将生成的.class文件自动保存到与包名同名的子目录下。

2.import语句

import关键字用于导入指定包层次下的某个类或全部类,import语句应放在package语句之后,类定义之前,其语法格式如下:

import 包名.类名     //导入单类
import 包名.* //导入包层次下的全部类
3.给Java应用程序打包

在实际开发中,通常会将一些类提供给别人使用,直接提供字节码文件会比较麻烦,所以一般会将这些类文件打包成jar文件,以供别人使用。jar文件的全称是Java Archive File,意思就是Java归档文件,也称为jar包。将一个jar包添加到classpath环境变量中,Java虚拟机会自动解压jar包,根据包名所对应的目录结构去查找所需的类

通常使用jar命令来打包,可以把一个或多个路径压缩成一个jar文件。jar命令在JDK安装目录下的bin目录中,直接在命令行中输入jar命令,即可查看jar命令的提示信息

jar命令主要参数如下:

(1)c:创建新的文档

(2)v:生成详细的输出信息。

(3)f:指定归档的文件名

12.Lambda表达式

“->”可以称作箭头操作符或者Lambda操作符。当使用Lambda表达式进行代码编写的时候就需要使用这个操作符。箭头操作符将Lambda表达式分成左右两部分,在操作符的左侧代表着Lambda表达式的参数列表(接口中抽象方法的参数列表),在操作符的右侧代表着Lambda表达式中所需执行的功能(是对抽象方法的具体实现)Lambda表达式的语法格式如下:

(parameters)->experssion或(parameters) ->{statements;}

上述语法还可以写成:

无参数无返回值:()->具体实现

有一个参数无返回值:(x)->具体实现,或x->具体实现

有多个参数,有返回值,并且Lambda体重有多条语句:(x,y)->{具体实现}

若方法体只有一条语句,那么大括号和return都可以省略

九.异常

异常是一个在程序执行期间发生的事件,它中断了正在执行程序的正常指令流。在程序中,错误可能产生于程序员没有预料到各种情况或是超出了程序员可控范围的环境因素,为了保证程序有效地执行,需要对发生的异常进行相应的处理。

1.异常的类型

  1. Error类:Throwable的一个子类,代表错误,该体系描述了Java运行系统中的内部错误以及资源耗尽的情形。该类错误是由Java虚拟机抛出的,如果发生,除了尽力使程序安全退出外,在其他方面是无能为力的。
  2. Exception类是另外一个重要的子类,它规定的异常是程序自身可以处理的异常。异常和错误的区别在于异常是可以被处理的,而错误是不能够被处理的。

2.异常的处理

1.使用try-catch处理异常

异常捕获结构:

try{                 //捕获异常,该块用于监控可能发生异常的代码块是否发生异常,如果异常发生了   
程序代码1 //就会将产生的异常类对象抛出,并转向catch中继续执行。
}catch(异常类型1 e1){ //处理异常,一个try块后面可以有多个catch块,每个catch块可以处理的异常类型
程序代码2 //由异常处理器参数指定。
}catch(异常类型 e2){
程序代码3
}finally{ //进行最终处理,不管程序是否发生异常最终都会执行的代码块。当包含catch子句
程序代码4 //时,finally字句是可选的;当包含finally字句时,catch子句是可选的。
}

不管程序是否发生异常,还是在try块中使用return语句结束,finally块都会执行。

2.使用throws关键字抛出异常

如果方法不捕获被检查出的异常,那么方法必须声明它可以抛出的这些异常,用于告知调用者此方法有异常。Java通过throws子句表明方法可抛出的所有异常

数据类型 方法名(形参列表)throws 异常类1,异常类,...,异常类n{
方法体;
}

throws声明的方法表示此方法不处理异常,而交给方法的调用者进行处理。因此,不管方法是否发生异常,调用者都必须进行异常处理。

3.使用throw关键字抛出异常

针对用户希望能亲自进行异常类对象的实例化操作,自己手动抛出异常

throw new 异常对象();

4.自定义异常

在特定的问题领域,可通过扩展Exception类或RuntimeException类来创建自定义的异常。异常类包含和异常相关的信息,这有助于负责捕获异常的catch代码块准确地分析并处理异常。

(1)创建自定义异常类并继承Exception基类,如果自定义Runtime异常,则继承RuntimeException基类

(2)此方法中通过throw关键字抛出异常对象。

(3)如果在当前抛出异常的方法中处理异常,可以使用try-catch语句块捕获并处理,否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作。

(4)在出现异常方法的调用者中捕获并处理异常。

5.断言

断言语句用于确保程序的正确性,以避免逻辑错误

assert 布尔表达式;
assert 布尔表达式:消息;

使用第一种格式,当布尔类型表达式值为false时,抛出AssertionError异常,如果使用第二种格式,则输出错误消息。在默认情况下,断言不起作用,可用-ea选项激活断言

java -ea类名
java -ea:包名 -da:类名

十.多线程

线程是比进程更小的执行单位,线程是在进程的基础上进行的进一步划分。所谓多线程是指一个进程在执行过程中可以产生多个更小的程序单元,这些更小的单元称为线程,这些线程可以同时存在、同时运行,一个进程可能包含多个同时执行的线程

进程是程序的一次动态执行过程,需要经历从代码加载、代码执行到执行完毕的一个完整过程,这个过程也是进程本身从产生、发展到最终消亡的过程。每个运行中的程序就是一个进程。一般而言,进程在系统中独立存在,拥有自己独立的资源,多个进程可以在单个处理器上并发执行且互不影响。

操作系统可以同时执行多个进程,进程可以同时执行多个任务,其中每个任务就是线程。eg.杀毒软件程序是一个进程,那么它在喂计算机体检的同时可以清理垃圾文件,这就是两个线程同时运行。

1.线程的创建

1.继承Thread类创建线程

Java提供了Thread类代表线程,它位与java.lang包中

(1)定义Thread类的子类,并重写run()方法,run()方法称为线程执行体。

(2)创建Thread子类的实例,即创建了线程对象。

(3)调用线程对象的start()方法启动线程。

启动一个新线程时,需要创建一个Thread类实例。

public class TestThread{
public static void main(String[] args){
SubThread st1 = new SubThread1(); //创建SubThread1实例
SubThread st2 = new SubThread1();
st1.start(); //开启线程
st2.start();
}
}
class SubThread1 extends Thread{
public void run(){ //重写run()方法
for(int i = 0;i<4;i++){
if(i%2!=0){
System.out.println(Thread.current().getName()+":"+i);
}
}
}
}
2.实现Runnable接口创建线程

(1)定义Runnable接口实现类,并重写run()方法

(2)创建Runnable实现类的示例,并将实例对象传给Thread类的target来创建线程对象。

(3)调用线程对象的start()方法启动线程。

3.使用Callable接口和Futrue接口创建线程

重写run()方法实现功能代码有一定局限性,这样做方法没有返回值且不能抛出异常

Java提供了Callable接口来解决这样的问题,接口内有一个call()方法可以作为线程执行体,call()方法有返回值且可以抛出异常。

(1)定义