Java学习笔记

给你一囗甜甜゛ 提交于 2019-12-02 10:51:59

个人的学习笔记,仅做记录

基础知识

  • JDK,Java开发工具包,包含所需工具和JRE
  • JRE,Java运行环境,包含运行Java所需的核心类库和JVM
  • JVM,Java虚拟机,保证Java跨平台使用,本身不能跨平台使用

第一个Java程序

public class HelloWorld {
    public static void main(String[] args){
        System.out.println("HelloWorld");
    }
}

1.Java程序的最小单位是类,一个Java程序至少有一个类。

2.Java程序的入口是main方法,main方法的格式是固定的:System.out.println(“HelloWorld”);

3.在控制台输出内容的语句:System.out.println(“要输出的内容”);

4.IDEA中文件的解释:在IDEA中新建一个项目后有一些文件,.idea目录和.iml文件是IDEA开发工具使用的配置文件,不需要操作,可以隐藏。src文件夹,所编写的源文件存放在这个目录下。out目录,是Java程序的输出目录,Java程序运行的时候,生成后缀名为.class的文件存放在该目录下,点击运行按钮,生成.class文件叫做字节码文件,生成字节码文件的过程叫编译。External Libraries,即扩展类库,是Java程序编写和运行所依赖的JDK中的类。

高级语言运行机制

计算机高级语言按其执行方式分为:编译型和解释型两种。编译型语言指使用专门的编译器,针对特定操作系统,将某种高级语言一次性翻译成可被计算机识别的机器码,并包装成可以被该系统识别的可执行格式,这个过程称为“编译”。因为是一次性编译,所以编译后的代码可以脱离开发环境,运行效率也比较高。但编译时是编译成了特定平台的机器码,所以无法直接将编译后的文件拿来跨平台运行,C、C++、Swift等属于编译型语言。

解释型语言是指使用专门的解释器,对源文件逐行解释成特定平台的机器码并立即执行的语言。它不会对整体进行编译和链接处理,解释型语言相当于把编译和解释过程同时进行。所以解释型语言执行效率比较低,而且不能脱离解释器独立运行,但解释型语言跨平台比较容易,只需提供特定平台的解释器就可以,JavaScript、Python、Ruby等属于解释型语言。

而Java却有些争论,Java程序需要编译这个过程,但这个过程不会生成特定平台的机器码,而是生成字节码(.class),这些字节码是不可执行的,需要由Java的解释器,即JVM来进行逐行解释。在《疯狂Java讲义》中把Java定义为既不是纯粹的编译型语言,也不是纯粹的解释型语言,而我在查找很多个人博客中发现,基本上所有人都把Java作为解释型语言,所以Java还是先作为解释型语言来理解。

Java程序开发与运行原理

其中javac.exe(编译)和java.exe(运行)均来自JDK中。

JVM是Java跨平台的关键部分,只要为不同平台实现了相应的虚拟机,编译后的字节码文件都可以在该平台执行,它相当于一个兼容不同平台的转换器,向上提供给Java的字节码程序接口是一样的,向下适应不同平台。

IDEA常用快捷键(初学者切记不要使用IDE,最后可能会连关键字都拼不出来)

编码规范

大括号成对、对齐写;左大括号前加空格;代码缩进;方法和程序块之间空行;并排语句加空格;运算符两侧加空格。

**注释分类:单行注释,//注释文字;多行注释,/ 注释文字 /;文档注释(用来解释类和方法),/ 注释文字 */

关键字

常量

分类:字面值常量和自定义常量

字面值常量:A.字符串常量,“hello world” B.整数常量,12,-2 C.小数常量,12.22 D.字符常量,‘a’,‘2’ E.布尔常量,true false F.空常量,null。

Java中的常量又称号“final修饰的变量”,当一个变量被final修饰的时候,该变量只有一次赋值的机会,final不可以修饰参数,不能在方法中给参数赋值

public class HelloWorld {

    public void method1(final int j) {
    	j = 5; //不能执行
    }
}

形参在调用method1方法时,由系统根据传入的参数来完成初始化,参数传进来的值j被修饰后不可以再次赋值。

变量

变量的定义格式:数据类型 变量名 = 初始化值。数据类型:变量变化的范围就是数据类型。

数据类型

分为基本类型和引用类型,基本类型下分整数型、浮点型、字符型、布尔型;引用类型下分类、接口、数组

定义变量的注意事项:1.定义long类型变量时,后边要加字母L(大小写均可)2.浮点型默认是double类型,定义float类型变量时,后边要加字母F(大小写均可);变量使用时,未赋值,不能使用。只能在其作用域中使用。一行可以定义多个变量,但最好不这样写。

类型转换的分类:分为自动(隐式)类型转换和强制(显示)类型转换。自动类型转换时,会将小类型转换成大类型,运算结果是大类型。例如:byte类型和int类型计算,会先将byte类型提升为int类型再计算,结果也是int类型。强制类型转换时,手动将大类型转换成小类型,运算结果是小类型,转换格式:小类型 变量名=(小类型)大类型数据。

注意:当且仅当大类型数据可以转换成小类型数据时,才进行转换,否则会造成精度损失。

标识符

由英文大小写字母、数字、下划线、和$组成。不能以数字开头,不能是关键字,并严格区分大小写。

命名规范:

  • 类和接口:首字母大写,如果有多个单词,每个单词首字母大写,即采用驼峰命名。
  • 变量和方法:首字母小写,如果有多个单词,从第二个单词开始首字母大写:getName,studyJava
  • 常量名(自定义常量):所有字母都大写,多个单词用下划线隔开:MAX_VALUE
  • 包名:全部小写,如果有多级,用点号(.)隔开,遵循域名反写的格式。

运算符

运算符是对常量和变量进行运算操作的符号,常见的运算符有:算术运算符、赋值运算符、关系运算符、逻辑运算符和三元运算符。

  • 算数运算符:+、-、*、/、%、++、–。其中在使用除法运算符(/)时,整数除以整数结果还是整数,想要得到小数,就必须有浮点数参与运算。

  • 加法运算时:加号两边是数值型数据时,进行加法运算;加号两边有任意一边是字符串时,进行字符串的拼接;字符型数据参与算术运算,时用字符在计算机中存储的数据来运算的。

int a = 10;
char ch = 'a';
System.out.println("hello" + "world");
System.out.println("hello" + 10);
System.out.println("hello" + 10 + 20);
System.out.println(10 + 20 + "hello");

//结果依次为:
>>>helloworld
>>>hello10
>>>hello1020
>>>30hello
  • 自增和自减运算:在单独使用时,放在变量前或后结果一样。参与运算时,放在变量前,先自增(自减),再进行其它运算;放在变量后,先以原值进行其它运算,再自增(自减)
//单独使用
int a = 5;
a++;
++a;
System.out.println(a);
//输出结果都是6

//参与运算
int b = a++;
System.out.println(a);
System.out.println(b);
//输出结果分别为6、5
int b = ++a;
System.out.println(a);
System.out.println(b);
//输出结果分别6、6
  • 赋值运算符,分为基本赋值运算符(=)和扩展赋值运算符(+=、-=、/=、*=、%=),扩展赋值运算符有着省略强制类型转换的操作。
short s = 2;
s = s + 1;//此时会报错,因为s + 1的结果是一个int类型的数据,int型数据不能赋值给short类型的变量
s += 3;
System.out.println(s);
  • 关系运算符,用来描述两个变量值的关系,关系运算符的运算结果都是布尔类型。
  • 逻辑运算符,用来判断“并且”、“或者”、“除非”等逻辑关系,逻辑运算符两端一般连接值为布尔类型的关系表达式。常见的逻辑运算符有:&&:逻辑与,并且的关系,有flase则整体为false;||:逻辑或,或者的关系,有true则整体为true;!:逻辑非,表示否定,取反。

选择

Scanner的基本使用

  • 使用Scanner类需要先导包
import java.util.Scanner;
  • 使用一个类前,先创建它的对象
Scanner sc = new Scanner(System.in);
  • 接收数据,此代码执行时,系统会等待用户录入int型数据,如果输入的数据非int型,可以使用异常处理来确保程序继续运行。
int i = sc.nextInt();

循环

for循环while循环和do while循环主要有以下不同:

  • 格式的不同,for循环各部分形成一个整体,while和do while的初始化语句与循环定义分离;while、do while的初始化语句、控制语句一般都会省略,而for循环一般不省略。
  • 初始化语句的不同,定义不同不同;作用域不同:for循环初始化语句仅限循环内使用,while和do while的初始化语句可以在循环外使用。
  • 循环体执行次数的不同,for和while执行0-n次;do while执行1-n次,即至少执行一次。
  • 使用场景的不同,for和while可以互换,但while格式更简洁;仅当循环体至少需要执行一次时使用do while。

在使用循环控制语句时,不要用浮点数进行比较,浮点数都是近似值,可能导致不精确的循环次数和结果。例如:

double item = 1;
double sum = 0;
while(item != 0) {
    sum += item;
    item -= 0.1;
}
System.out.println(sum);

上面的代码貌似是正确的,但实际上是个无限循环,不能保证item的值正好为0。

**带标号的循环:**标号,即循环的名称,给循环定义一个标号,就可以根据需要结束或跳转到指定循环,常用于多层嵌套循环。

标号:for() {} //while和do while类似
break 标号;//结束指定标号的循环
continue 标号;//跳转到指定标号的循环继续执行
public class Breakdemo {
    public static void main(String[] args) {
        label_class:for (int i = 1; i < 4; i++) {//需要结束的循环
            for (int j = 1; j < 11; j++) {
                System.out.println("正在查找第"+ i +"班的第"+ j +"个学生");
                if(i == 2) {
                    System.out.println("已找到该同学,循环结束");
                    break label_class;//在想要结束的循环前加标号,若不加此时只结束内层循环
                }
            }
            System.out.println();
        }
    }
}

输入输出重定向:如果要输入大量数据值,可以将这些数据用空格分隔,存入一个叫input.txt的文件中,使用java SentinelValue < input.txt命令,这个命令叫输入重定向;类似还有输出重定向,使用Java ClassName > output.txt将输出发送给名叫output.txt的文件。这两个命令可以同时使用,java SentinelValue < input.txt > output.txt从input读入数据,输出到output。

Random类的简单使用

Random类,用于产生随机数。使用过程:1>使用import java.util.Random导包;2>Random r = new Random()创建一个对象;3>int number = r.nextInt(10)获取0-9int型随机数,此时括号内的10其实是表示[10),即有0无10;

方法

在Java中方法即是函数,一个方法有以下几部分构成:修饰符:public static;*返回值类型:*方法执行结果的数据类型;*方法名:*方法的名称,符合标识符命名规则即可;*参数列表:*方法执行需要的条件,其中参数类型可以是基本类型,也可以是引用类型,参数名即变量名;*方法体语句:*完成特定功能的代码;return:用于结束方法,若方法没有返回值,则它的返回值类型为void,比如main方法。

引用类型

**基本类型的变量作为参数传递时,传的是值。引用类型的变量作为参数传递时,传的是地址值。**在java里面除去基本数据类型的其它类型都是引用数据类型,自定义的class类都是引用类型。按值传递:方法调用时,传递的参数是按值的拷贝,传递后的值与原值互不相关,基本类型中,形参的改变不影响实参。按引用传递:方法调用时,传递的参数是引用的地址,也就是变量所对应的内存空间的地址。基本类型分为四类八种,引用类型是除了四类八种以外的,包括数组、字符串、类、接口等。

方法的定义与调用

定义一个方法分三步:确定方法名;确定返回值类型;确定参数列表。调用方法分两步:通过方法名调用方法;根据形参列表将实参传递给方法。

定义方法的注意事项:方法必须定义在类中。2.方法之间是平级关系,不能相互嵌套。3.方法没有返回值时也要有返回类型:void。4.方法返回值类型为void时,可以省略return语句。5.return语句后的数据类型必须和返回类型匹配。6.return之后不能再放置语句。

调用方法

定义在方法头中的变量称为形参,调用方法时传递给参数的值称为实参,方法名和参数列表一起构成方法签名,在方法头中需要对每个变量单独进行数据类型声明。实参在传递时,必须要与形参在次序和数量上匹配,在类型上兼容。类型兼容指不需要经过显示的类型转换,实参就可以传递给形参,适用于将小类型传给大类型,例如,将int型的实参转递给double型的形参。

如果方法返回的是一个值,对方法的调用通常当做一个值处理。如果方法返回void,对方法的调用必须是一条语句。

main中的语句可以调用main方法所在类中定义的其他方法,也可以调用别的类中定义的方法。

方法重载

在同一个类中的多个方法,它们的方法名相同,参数列表不同,这样的情况,称为方法重载,方法重载与返回值类型和修饰符无关。当实现的功能相同,但具体的实现方式不同时,可以通过定义名称相同,参数不同的方法,来识别和管理类中的方法。方法的重载可以使程序更加清楚,以及更加具有可读性。

数学函数、字符和字符串

常用数学函数

三角函数方法

方法 描述
sin(radians) 以弧度为单位的正弦函数
cos(radians) 以弧度为单位的余弦函数
tan(radians) 以弧度为单位的正切函数
toRadians(degree) 将以度为单位的角度转换为弧度表示
toDegree(radians) 将以弧度为单位的角度转换为度表示
asin(a) 以弧度为单位的反三角正弦函数
acos(a) 以弧度为单位的反三角余弦函数
atan(a) 以弧度为单位的反三角正切函数

指数方法

方法 描述
exp(x) 返回e的x次方
log(x) 返回x的自然对数
log10(x) 返回以10为底x的对数
pow(a,b) 返回a的b次方
sqrt(x) 对于>=0的数,返回平方根

min、max和abs方法

min和max方法用于返回两个数(int、long、float、double)的最小值和最大值,min(3,2)返回2,max(3.5,5.5)返回5.5。abs方法返回一个数的绝对值。

取整方法

方法 描述
ceil(x) x向上取整到最近的数,以双精度值返回
floor(x) x向下取整到最近的数,以双精度值返回
rint(x) x取整到最近的整数,如果x与两个整数距离相同,偶数的整数返回一个双精度值
round(x) 返回一个最接近的 int、long 型值,四舍五入。

字符数据类型和操作

字符数据类型用于表示单个字符,char型数据可以转换成任意一种数值类型,反之亦然。两个字符可以使用关系操作符进行比较,这里比较的是两个字符的Unicode值,Java中的Character类提供一系列方法用于字符测试:

Character 类用于对单个字符进行操作。

方法 描述 方法 描述
isLetter() 是否是一个字母 isLowerCase() 是否是小写字母
isDigit() 是否是一个数字字符 toUpperCase() 指定字母的大写形式
isWhitepace() 是否是一个空白字符 toLowerCase() 指定字母的小写形式
isUpperCase() 是否是一个大写字母 toString() 返回字符的字符串形式,字符串的长度仅为1

String类型

字符串是一个字符序列,String与System和Scanner都是Java库中一个预定义的类。String不是基本类型,而是引用类型,引用类型声明的变量是引用变量它引用一个对象。

常用方法

方法 描述 方法 描述
length() 返回字符串总的字符数 toUpperCase() 返回一个新字符串,其中所有字母大写
charAt(index) 返回字符串中指定位置的字符 toLowerCase() 返回一个新字符串,其中所有字母小写
concat(s1) 将本字符串与s1字符串连接并返回 trim() 返回一个新字符串,去掉两边的空白字符

字符串和数字间的转换

可以将数值型字符串转换成数值,使用Integer.parseInt()方法,例如:

int value = Integer.parseInt("233")
//结果为233

数组

为了存储多个数据值,使用数组,数组是用来存储同一种数据类型多个元素的容器。数组创建后长度不可改变,即使将数组的所有元素清空,它所占有的空间依然保留。

数组的定义格式1

//数据类型[] 数组名 = new 数据类型[长度];
int[] arr = new int[3];

数据类型:即数组中存储元素的数据类型,可以是基本数据类型,也可以是引用数据类型,只要数组元素数据类型相同就可以。[]:表示数组。数组名:数组的变量名,遵循标识符命名规范。new:创建数组的关键字,通过new开辟内存空间。长度:即数组的长度,数组最多能够存放元素的个数,数组长度在定义时指定,不可更改。

数组的定义格式2

//数据类型[] 数组名 = new 数据类型[]{元素1,元素2...};
int[] arr2 = new int[]{1,2,3};//定义时元素确定,避免了内存的浪费

数组的定义格式3

//数据类型[] 数组名 = {元素1,元素2...};
int[] arr3 = {1,2,3};

数组的访问

通过数组的索引访问数组的元素,格式如下:

/*数组名[索引]
取值:数组名[索引]
赋值:数组名[索引] = 值;
*/

int[] arr3 = {1,2,3};
System.out.println(arr3[0]);

int[] arr3 = {1,2,3};
arr3[1] = 4;
System.out.println(arr3[1]);

数组的复制

数组的长度定义后就不可以改变,但数组可以进行复制。

System.arraycopy(src,srcPos,dest,destPos,length)
  • src:源数组
  • srcPos:从源数组复制数据的起始位置
  • dest:目标数组
  • destPos:复制到目标数组的起始位置
  • length:复制的长度
/*
	数组a:1 2 3 4 5
	数组b:6 7 8 9 10
	数组c:11 22 33 44 55 66 77 88 99 100
*/
//数组a和数组b合并到数组c中
package arraydemo;

public class arraycopy {
    public static void main(String[] args) {
        int[] a = new int[]{1,2,3,4,5};
        int[] b = new int[]{6,7,8,9,10};
        int[] c = new int[]{11,22,33,44,55,66,77,88,99,100};
        System.arraycopy(a,0,c,0,5);
        System.arraycopy(b,0,c,5,5);

        for (int i = 0; i < c.length; i++) {
            System.out.print(c[i] + " ");
        }
    }
}

//输出结果:1 2 3 4 5 6 7 8 9 10

数组的遍历

数组中的最大索引为数组长度-1;数组中未手动赋值的元素,有默认值0;直接输出数组变量名,得到的是数组的内存地址值。

public class Arrdemo1 {
    public static void main(String[] args) {
        int[] arr = new int[5];
        arr[0] = 1;
        arr[1] = 2;
        arr[2] = 3;

        System.out.println("数组的长度是:"+arr.length);
        System.out.println("数组的最大索引是:"+(arr.length-1));
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
}

foreach循环

对数组遍历时,使用foreach循环可以简化遍历赋值的操作。

public class HelloWorld {
    public static void main(String[] args) {
        int values [] = new int[]{18,62,68,82,65,9};
        //常规遍历
        for (int i = 0; i < values.length; i++) {
			int each = values[i];
			System.out.println(each);
		}
        
        //foreach循环遍历
        for (int each : values) {
			System.out.println(each);
		}
        
    }
}

其中变量each的数据类型要与values数据类型相同,当以其他顺序遍历数组时,还是要使用下标变量。

数组的初始化

数组的初始化指在内存中为数组开辟连续空间并为每个元素赋值的过程。初始化分为动态初始化和静态初始化。

动态初始化:只指定长度,由系统给出默认值,整数型:0,浮点型:0.0,字符型:‘\u000’(空字符),布尔型:false。引用类型:null。

int[] arr = new int[5];

静态初始化:给出初始化值,由系统决定数组长度。

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

Java程序的内存分配

  • 方法区:存储可运行的class文件,包含方法,静态成员,常量等。
  • 栈:方法运行时使用的内存,特点是“后进先出”,比如main方法。
  • 堆:存储new出来的数组或对象。
  • 本地方法栈:JVM在调用操作系统功能时使用。
  • 寄存器:CPU使用。
  • 数组类型:变量arr存储的是数组在堆内存中的地址值,而不是数组元素的值,变量arr通过内存地址引用内存中的数组,所用数组是引用类型

数组使用中两个常见问题

  • 数组索引越界异常,当访问了不存在的索引时产生越界异常
public static void main(String[] args){
    int[] arr1 = new int[3];
    arr1[0] = 1;
    arr1[1] = 2;
    arr1[2] = 3;
    System.out.println(arr1[3]);//报错
}
  • 空指针异常,数组引用存储的值为null而非数组的地址值时产生空指针异常。
public static void main(String[] args){
    int[] arr1 = new int[3];
    arr1[0] = 1;
    arr1[1] = 2;
    arr1[2] = 3;
    System.out.println(arr1[0]);
    arr1 = null;
    System.out.println(arr1[02);//报错
}

二维数组

一维数组可以存储线性的元素,而二维数组可以存储矩阵或表。

声明二维数组

//数据类型[][] 数组名;
int[][] array1;
int array1[][];//这种也可以,但不推荐

array1[2][1] = 7;//将元素7赋值给数组第二行第一个元素

获取二维数组长度

二维数据实际上是每一个元素都是一维数组的数组,数组array1的长度是数组中元素的个数。用array1.length获取长度,元素array1[0]、array1[array.length-1]都是数组。可以使用array1[array.length-1].length获取长度。

定义一个二维数组int[][] x = new int[3][4],其中x.[0]、x[1]、x[2]都是一维数组,它们的长度都是4.

不规则数组

因为二维数组每一行都是一维数组,所以每行的长度可以不同,这样的数组称为不规则数组。

//创建不规则数组
int[][] array = {
    {1,2,3,4,5},
    {6,7,8,9},
    {10,11},
    {12}
}

数组 Arrays

Arrays是针对数组的工具类,可以进行排序、查找、复制、填充等。

复制数组

System.arraycopy类似,Arrays使用copyOfRange方法进行数组复制。

import java.util.Arrays;

public class Arraycopy1 {
    public static void main(String[] args) {
        int[] a = new int[]{1,2,3,4,5};
        int[] b = Arrays.copyOfRange(a,0,3);
        for (int i = 0; i < b.length; i++) {
            System.out.print(b[i] + " ");
        }
    }
}

//运行结果:1 2 3
/* copyOfRange(int[] original, int from, int to)
   第一个参数表示源数组
   第二个参数表示开始位置(取得到)
*/ 第三个参数表示结束位置(取不到)

转换为字符串

如果要打印一个数组,就需要用for循环遍历数组中的每个元素逐一打印,Arrays提供toString()方法,可以把数组转换成字符串,直接全部打印。但注意,打印的结果与用for循环遍历的结果大有不同。

import java.util.Arrays;
  
public class Arraydemo {
    public static void main(String[] args) {
        int a[] = new int[] { 18, 62, 82, 65, 9 };
        String content = Arrays.toString(a);
        System.out.println(content);
  
    }
}
//打印结果:[18, 62, 82, 65, 9]
//用for循环遍历打印的是:18 62 82 65 9

排序

Arrays提供sort()方法,可以对数组中的数据进行排序。

import java.util.Arrays;

public class Arraycopy1 {
    public static void main(String[] args) {
        int a[] = new int[] {18, 62, 68, 82, 65, 9};
        System.out.println("排序前:");
        for (int i = 0; i < a.length; i++) {
            System.out.print(a[i] + " ");
        }
        System.out.println();
        Arrays.sort(a);
        System.out.println("排序后:");
        for (int i = 0; i < a.length; i++) {
            System.out.print(a[i] + " ");
        }
    }
}


/*输出结果:
排序前:
18 62 68 82 65 9 
排序后:
9 18 62 65 68 82 
*/

搜索

使用binarySearch可以查询元素出现的位置,但必须用sort进行排序,而且如果数组内有多个相同元素,查询结果也是不确定的。更多关于binarySearch的分析见 https://blog.csdn.net/qq_40178464/article/details/79942814

import java.util.Arrays;

public class Arraycopy1 {
    public static void main(String[] args) {
        int a[] = new int[] {68, 68, 68, 68, 68, 9};
        for (int i = 0; i < a.length; i++) {
            System.out.print(a[i] + " ");
        }
        System.out.println();
    System.out.println(Arrays.binarySearch(a,68));  
    }
}

//输出结果为:2

判断是否相同

使用Arrays.equals()方法可以比较两个数组的内容是否一样,相同返回true,否则返回false。

import java.util.Arrays;
 
public class Arraydemo {
    public static void main(String[] args) {
        int[] a = new int[] { 18, 62, 68, 82, 65, 9 };
        int[] b = new int[] { 18, 62, 68, 82, 65, 8 };
 
        System.out.println(Arrays.equals(a, b));
    }
}

//输出结果:false

填充

使用Arrays.fill()方法,将一个值填充到整个数组。

import java.util.Arrays;
  
public class Arraydemo {
    public static void main(String[] args) {
        int[] a = new int[5];
  		
        Arrays.fill(a, 5);
  		for (int i = 0; i < a.length; i++) {
            System.out.print(a[i] + " ");
        }
    }
}

//输出结果:5 5 5 5 5

面向对象

类主要由属性和行为构成,而类是一系列具有相同属性和行为的事物的统称。对象是某一类事物的某个具体存在,类是抽象概念,对象是具体体现,例如:手机-类,我的小米8手机-对象。抽象是把一系列相关事物共同的属性和行为提取出来的过程。

定义一个类:定义类的过程,就是把相关事物共同的属性和行为抽取出来的过程,事物的属性,在类中叫成员变量,事物的行为,在类中叫成员方法。成员变量定义在类中、方法外;成员方法要去掉static修饰符。例如,一个手机类,型号(brand)、品牌(model)、名称是它的属性,打电话(call())、玩游戏(playGame())是它的行为。

创建一个类

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

使用一个对象

对象名.变量名
对象名.方法名(...)

定义一个学生类并调用方法

public class Student {
    String name;
    int age;

    public void pray() {
        System.out.println(age + "岁的" + name + "正在祈祷...");
    }

    public static void main(String[] args) {
        Student s = new Student();
        s.name = "八云紫";
        s.age = 999999;
        s.pray();
    }
}

运行结果

999999岁的八云紫正在祈祷...

使用变量的规则

局部位置:方法的参数列表或方法体语句(方法内部)。成员位置:类中或方法外。Java中使用变量的规则:“就近原则”,如果局部位置有,就使用,没有就去本类的成员位置找,有就使用,没有则去父类中找。

成员变量和局部变量的区别:

  • 定义位置:成员变量在类中,方法外;局部变量在方法中或形参中。
  • 初始化值:成员变量有默认初始化值;局部变量无默认初始化值,必须赋值后才能使用。
  • 作用范围:成员变量在类中,局部变量在方法中。
  • 内存中的位置:成员变量存储在堆内存;局部变量存储在栈内存。
  • 生命周期:成员变量随着对象的创建而存在,随着对象的消失而消失;局部变量随着方法的调用而存在,随着方法调用完毕而消失。

封装

封装是Java主要特性之一,封装提高了代码的安全性,提高复用性,将复杂的事情简单化。Java中的封装体有两个,方法和类。使用方法和类时,调用者不知道具体实现,而且可以重复调用。

private关键字

private是一种私有的访问权限,用来修饰类的成员,被修饰的成员只能在本类中访问。用法private 数据类型 变量名;或 private 返回值类型 方法名(参数列表){}private一般用来修饰成员变量,public一般用来修饰成员方法。

public static void main(String[] args){
    Student stu = new Student();
    stu.setAge(23);//设置年龄值
    System.out.println(stu.setAge());
    stu.study();//调用study()方法
}

public class Student {
    private int age;
    public void setAge(int a) {
        a > 200 || a <= 0 ? (age=1) : age = a;//对age的值进行限定
        
        public int getAge(){
            return age;
        }
        
        public void study(){
            System.out.println("正在写作业")
        }
    }
}

this关键字

这里的this就是C++中的this指针,为解决局部变量和全局变量重名的问题使用。它表示本类对象的引用,本质上是一个对象,每个普通方法都有一个this,谁调用该方法,this就指向谁,使用时从本类开始找。

用法

this.属性名;
this.方法名(参数);

构造方法

Java总通过new关键字创建对象,并在内存中开辟空间,然后使用构造方法初始化对象。

//构造方法的格式
修饰符 构造方法名(参数列表){
	//方法体
}
/*
方法名必须与类名相同
没有返回值(但可以写return,return表示方法结束)
没有返回值类型(void都不能写!)
若未提供任何构造方法,系统会给出默认无参构造
若已提供任何构造方法,系统不再提供无参构造
构造方法可以重载
*/

继承

继承的优点:

  • 功能复用,直接将已有的属性和行为继承过来,实现代码的复用。
  • 便于扩展新功能,在已有功能的基础上,更容易建立扩充新功能。
  • 结构清晰,简化认识,同属于一个继承体系的相关类,他们之间结构层次清晰,简化了人们对代码结构的认识。
  • 易于维护 ,不同类之间的继承关系,让这些事物之间保持一定程度的一致性,大大降低维护成本。

继承的缺点:

  • 打破了封装性和高耦合性。

继承关系中成员变量使用的特点:遵循“就近原则”,局部位置有就使用,没有就去本类的成员位置中找,有就使用,没有就去父类的成员位置找,有就使用,没有报错。若子类父类变量重名,使用子类变量用this,使用父类变量用super。

访问父类变量的方法:super.父类变量名;super是父类内存空间的标识,即当前对象父类的引用,使用时从父类开始找。对象初始化顺序是,先初始化父类内容,再初始化子类内容。

继承关系中类成员方法的使用:方法的查找遵循就近原则,查找的顺序是:本类->父类->更高的父类…,访问父类方法的方式:super.父类方法名();,定义重名方法的前提是:父类功能不能完全满足现实需求,扩展父类功能,或父类功能已过时,重新实现父类功能。

继承关系中构造方法的使用特点:子类所有构造方法的第一行都有一个默认的super()用来访问父类的无参构造方法,如果父类没有无参构造,可以通过super(参数)的形式访问父类的带参构造。

public static void main(String[] args){
    //创建子类对象
    Worker w = new Worker;
}
public class Person {
    public Person(String name){
        System.out.println("Person类的带参构造"+name)}
}
public class Worker extends Person{
    public Worker(){
        super("LIY");//初始化父类成员对象
        System.out.println("Workerl类的空参构造");
    }
}

方法重写

  • 定义:子类中出现和父类方法定义相同的方法的现象。
  • 解释:方法重写又叫方法的复写、覆盖,方法名、参数列表、返回值类型都相同。
  • 注意事项:父类私有方法无法重写,子类方法访问权限不能小于父类方法,子类不能比父类方法抛出更大的异常。

四大访问权限修饰符

修饰符 本类 同包下的类 不同包下的子类 不同包下的无关类
private Y
默认 Y Y
protected Y Y Y
public Y Y Y Y

private强调的是给自己使用;默认强调的是给同包下的类来使用;protected强调的是给子类使用;public强调的是给大家使用。

方法重写和方法重载的区别

重载 重写
方法名 相同 相同
参数列表 不同(个数或对应位置类型) 相同
返回值类型 无关 相同
修饰符 无关 访问权限不小于被重写方法
定义位置 同一个类 子父类中

方法重写:同一个类中出现两个或两个以上的同名方法时,这些方法的参数列表不同,称为方法重载。

方法重写:子类出现与父类一样的方法。

Java中继承的特点

  • 单继承:Java只支持类的单继承,但是支持多层继承,Java也支持接口的多继承。
  • 私有成员不能继承:只能继承父类的非私有成员。
  • 构造方法不能继承:构造方法用于初始化本类对象,创建子类对象时,需要调用父类构造初始化该对象的父类内容,若父类构造可以被继承,该操作会造成调用的混乱。
  • 继承体现了“is a”的关系:子类符合“is a”父类的情况下,才使用继承,其他情况不建议使用。

多态

Java中多态实现的三个步骤:要有继承(或实现)关系;要有方法重写;要有父类引用指向子类对象。

多态的应用场景:父类可以作为形参的数据类型,这样可以接受其任意的子类对象。

public class Test{
    public static void main(String[] args){
        Dog d = new Dog();//测试Dog类
        d.setName("二哈");
        showAnimal(d);
        
        Mouse m = new Mouse();//测试Mouse类
        m.setName("Jerry");
        showAnimal();
    }
    public static void showAnimal(Animal an){//父类型可以作为形参的数据类型,体现了多态
        an.eat();
    }
}

public class Dog extends Animal{
    public void eat(){
        System.out.println(getName() + "恰骨头");
    }
}
public class Mouse extends Animal{
    public void eat(){
        System.out.println(getName() + "恰奶酪");
    }
}

多态中成员变量的使用

多态关系中,成员变量是不涉及到重写的。

public class Test{
    public static void main(String[] args){
        Animal an = new Dog();//父类引用指向子类对象
        System.out.println(an.name);
    }
}
public class Animal{
    String name = "Animal";
}
public class Dog extends Animal{
    String name = "Dog";
}

多态的好处和弊端

  • 可维护性:基于继承关系,只需要维护父类代码,提高了代码的复用性,大大降低了维护程序的工作量。
  • 可扩展性:把不同的子类对象都当作父类看待,屏蔽了不同子类对象的差异,实现向后兼容。
  • 不能使用子类特用的成员。

当需要使用子类特用功能时,需要进行类型转换

向上转型(自动类型转换)
子类型转换成父类型,Animal animal = new Dog();
向下转型(强制类型转换)
父类型转换成子类型,Dog dog = (Dog)animal;

注意:只能在继承层次内进行转换,将父类对象转换成子类之前,使用instanceof进行检查。

抽象类

抽象类指包含抽象方法的类,使用abstract修饰;抽象方法指只有方法声明,没有方法体的方法,使用abstract修饰。

public abstract class Animal{//抽象类
    private String name;
    public abstract void eat();//抽象方法
}
public calss Mouse extends Animal{
    public void eat(){//继承抽象类后子类要进行方法重写
        System.out.println(getName() + "吃苹果")
    }  
}

当需要定义一个方法,却不明确方法的具体实现时,可以将方法定义为abstract,具体实现延迟到子类。

抽象类的特点

  • 修饰符:必须用abstract关键字修饰。
  • 抽象类不能实例化,简单说不能被new,只能创建子类对象。
  • 抽象类子类的两个选择,重写父类所以抽象方法或定义成抽象类。

抽象类成员的特点

  • 成员变量:可以有普通的成员变量,也可以有成员常量(final)。
  • 成员方法:可以有普通方法,也可以有抽象方法,抽象类不一定有抽象方法,有抽象方法的类一定是抽象 类(或接口)。
  • 构造方法:像普通类一样有构造方法,而且可以重载。
  • 抽象类中的成员比普通类多一种抽象方法,其它和普通类一样。

final关键字

  • 修饰类时,该类不能被继承,例如String、System都没有子类。
  • 修饰方法时,该方法不能被重写,final和abstract。
  • 修饰变量时,该变量是最终变量,也就是常量,只能赋值一次,不建议修饰引用类型数据,因为仍然可以通过引用修改对象的内部数据,此时地址是不变的,意义不大。

static关键字

  • static是静态的意思,用于修饰类的成员,被修饰的成成员变量称为类变量,类变量被本类所有对象共享,被修饰的成员方法称为类方法。
  • 调用方式:类名.成员变量名类名.成员方法名(参数)
  • 随意修改静态变量的值是有风险的,为了降低风险,可以同时使用final关键字修饰,即定义公有静态常量。
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!