一、初识JAVASE和环境搭建
1.java概述
(1)特点:
简单性、面向对象、分布式、健壮性、安全性、与平台无关性、多线程、动态性
(2)JDK:Java开发工具包(Java Development Kit)
JDK包括Java运行环境(JRE:Java Runtime Enviroment)和Java 开发环境(Java工具和Java基础类库)
JRE包括Java虚拟机(JVM:Java Virtual Machine)和Java核心类库
(3)JDK目录结构图
JDK的src.zip就是开发中经常要使用的Java基础类库(就是别人写好的类提供给我们使用)。
JDK的bin目录下有java.exe和javac.exe。有这两个可执行文件就能执行java命令和javac的命令
jre目录下的bin文件下的jvm.dll就是Java虚拟机,但是jvm.dll无法单独工作,jvm.dll启动后,会使用explicit的方法
(4)Java文件的运行?
首先Java文件都是以.java为后缀的文件(源文件),经过javac xxx.java命令后就能将xxx.java文件编译成xxx.class文件(字节码文件),然后通过java xxx(注意不要加后缀.class)命令后就会将xxx.class文件加载到jvm(Java 虚拟机上)来运行。javac命令和java命令都是需要javac.exe和java.exe来支持的。
2.环境搭建
(1)下载好了JDK就能够使用了,那为什么还要搭建环境呢?
在未使用开发工具前,运行java文件就需要使用到控制台,通过win + R键输入cmd打开控制台
可以看到前面带有路径,要想使用java命令和javac命令就必须切换到jdk的bin目录下,因为只有那个目录下才能使用java.exe和javac.exe可执行器,才能使用java命令和javac命令。但是每次打开控制台时都要切换目录太麻烦了,所以就要搭建环境。
搭建环境的步骤:
1.打开系统高级设置
2.打开环境变量-新建
3.在变量名输入JAVA_HOME,将JDK的路径赋值到变量值上,注意是JDK的目录,然后确定
4.系统变量就会多出一条JAVA_HOME的属性
5.双击Path路径
6.点击新建环境变量
7.输入%JAVA_HOME%\bin,点击确定-确定-确定就可以了,环境变量就配置好了
以上步骤就配置好了环境变量,当在控制台上输入java -version的时候,出现java版本信息也就配置好了,配置好了这个环境变量,当在控制台上输入java或者javac命令时就会去这些环境变量下的目录去找执行器。将%JAVA_HOME%\bin配置上后就会去这个目录下找java.exe和javac.exe来执行命令。所以在任何目录下输入java和javac命令就都能够使用了,而不会出现java不是可执行的命令或者外部命令的错误。
二、第一个程序HelloWorld与命名规范、注释
1.第一个程序HelloWorld
在D盘新建一个文件夹code,新建一个文本(以.txt为后缀的),将名字改为HelloWorld.java,后面改为.java是指将.txt改为.java,如果后缀名被隐藏了,就先打开后缀名,再改
在HelloWorld.java下输入以下代码,注意类名必须与文件名一致
public class HelloWorld{ //类名的命名规范每个单词的首字母大写AaaBbb
public static void main(String[] args){ //每个类只能有一个main方法,也就是主方法
System.out.println("Hello World!"); //在控制台打印Hello World!每句话后面用;结尾注意全得是英文状态下
}
}
public 表示权限修饰符,class表示类,HelloWorld表示类名
打开cmd,先切换目录到D盘的code文件下,然后先编译.java文件,再运行.class文件
这样就可以运行了
2.命名规范
(1)使用英文字母a-z、A-Z,数字0-9,以及这两个_$符号
(2)不能使用数字开头,可以使用英文字母和_$
开头,比如person
、_person
、_person$
,但是命名要做到见名知意
(3)不能使用关键字来命名,因为关键字是java给我们定义好的一些有特殊含义的单词
关键字(不用记,多写代码就知道了):
数据类型:boolean、byte、short、int、long、double、char、float、double。
包引入和包声明:import、package。
用于类和接口的声明:class、extends、implements、interface。
流程控制:if、else、switch、do、while、case、break、continue、return、default、while、for。
异常处理:try、catch、finally、throw、throws。
修饰符:abstract、final、native、private、protected、public、static、synchronized、transient、volatile。
其他:new、instanceof、this、super、void、assert、const、enum、goto、strictfp。
3.注释(编译时自动跳过)
单行注释://
多行注释:/*...*/
文档注释:/**...*/
三、Java基本语法
1.java的数据类型
(1)八大基本数据类型
整数类型:byte(1个字节,取值范围:-128到127)、short(2个字节)、int(4个字节)、long(8个字节)
浮点数类型:float(4个字节)、double(8个字节)
字符型:char(1个字节)
布尔型(真true假false):boolean(2个字节)
默认值:
整数类型:0、浮点数类型:0.0、字符型:' '、布尔型:false
public class Demo{
byte b = 1;
short s = 2;
int i = 3;
long l = 4;
long l = 4l;//后面的l可加可不加
float f = 1.0f;//标准写法
float f1 = 10;//这样写也可以
float f2 = 10.0;//错误写法,10.0后不加f默认为double类型,类型不匹配,编译报错
double d = 2.0;//小数类型不加f的默认为double类型
double d1 = 2.0d;//后面的d可加可不加
char c = 'a';
boolean b = true;//布尔型只能为true或者false
public static void main(String[] args){
}
}
(2)引用数据类型
类、接口、数组
默认值:
都是null
2.变量和常量
(1)变量
在程序运行中不断变化的量就是变量;
(2)常量(被final修饰)(常量的命名全部都是大写,不同单词以_分隔:ABC_DEF)
在程序运行中从不变化的量就是常量比如圆周率兀、对数底数e
public class Demo{
int age = 10;//定义一个变量,初始值为10
final double PI= 3.14;//定义一个常量为3.14,用final修饰的量就是常量
public static void main(String[] args){
age = 20;//在java中=号为赋值的意思,==号才表示等于的意思,不同于数学
PI = 4.14;//将4.14赋值给PI,但是PI是被final修饰的,是一个常量,不能被修改,所以编译报错,不能修改
System.out.println(age);
}
}
(3)局部变量(方法里,语句块里)(注意:局部变量在使用前必须要给一个初始值)
在方法里定义的变量或者语句块内定义的变量就是局部变量
public class Demo{
public static void main(String[] args){
int age = 10;//在方法里定义的变量就是局部变量,只在这个方法{}内有效
for(int i=0;i<10;i++){//在语句块里定义的变量也是局部变量
System.out.println(i);
}
}
}
(4)全局变量(类里,static修饰)(注意:属性或者全局变量不一定要给初始值,如果没给初始值,就由jvm给默认值)
在类里定义的变量并且用static修饰的变量就是全局变量
public class Demo{
public static double PI = 3.14;//全局变量,如果不想被修改,还可以加final关键字修饰
int a = 10;//属性
public static void main(String[] args){
}
}
(5)数据类型的自动转换
当byte(1个字节)、short(2个字节)、char(2个字节)、int(4个字节)做运算的时候,全部转化为int数据类型再进行计算。为什么会转成int呢?因为每一种数据类型都有自己的取值范围,当小的数据类型跟大的数据类型来做计算时,得出的值要是大于小的数据类型的范围呢,所以要用范围最大的那个数据类型来接收。字节大的不一定取值范围大,比如float就比long的范围大。
范围:
byte(1个字节):-128 - 127
short(2个字节):-32768 - 32768
int(4个字节):-2147483648-2147483648
long(8个字节):-9233372036854477808-9233372036854477808
float(4个字节):-3.40292347E+38-3.40292347E+38
double(8个字节):-1.79769313486231570E+308-1.79769313486231570E+308
char(2个字节):‘ \u0000 - u\ffff’
boolean(1个字节):false/true
int之前的自动转换
public class Demo{
public static void main(String[] args){
byte b = 1;
short s = 1;
char c = 'a';//对应ASCLL码表,每一个字符对应一个数字,a对应数字97
int i = 1;
int sum = b + s + c + i;//全部转为int的数据类型来计算,所以最后要用int的数据类型来接收
System.out.println(sum);
byte b1 = 1;
byte b2 = 1;
int i1 = b1 + b2;//尽管b1、b2是同种数据类型,但同样会转成int类型来做计算
}
}
int之后的自动转换
public class Demo{
public static void main(String[] args){
int i = 1;
long l = 1;
float f = 1.0f;
double d = 1.0;
double sum = i+ l + f + d;//自动提升为范围最大的double类型
System.out.println(sum);
long l1 = 10;
long l2 = 10;
long l3 = long1 + long2;//不同于上面,int之后的数据类型如果是相同的数据类型进行操作,不用提升,就用这种数据类型类接收
}
}
(6)数据类型的强制转换(在变量前加(要强制转换成的类型))
public class Demo{
public static void main(String[] args){
int i = 128;
byte b = (byte)i;
System.out.println(b);
}
}
i的值为128,强转为byte型,但是byte型的取值范围为-128到127,i为128强转为byte型,就会损失精度,变成其他的数字,所以在强转的时候,有可能会损失精度。
3.运算符
(1)四则运算符
public class Demo{
public static void main(String[] args){
int a = 3;
int b = 2;
int add = a + b;
int sub = a - b;
int mul = a * b;
int div = a / b;
System.out.println(add);
System.out.println(sub);
System.out.println(mul);
System.out.println(div);
}
}
结果为5、1、6、1,那为什么a/b为1呢?不应该为1.5吗?看前面是用int的数据类型来接收的结果,所有这里的除法会做一个整除,将小数点的尾数去掉,不是四舍五入。
(2)运算符前后++和--
public class Demo{
public static void main(String[] args){
int i = 10;
int b = i++;
System.out.println(b + "," + i);//打印结果为b为10,i为11
int i1 = 10;
int b1 = ++i;
System.out.println(b1 + "," + i1);//打印结果为b1为11,i1为11
}
}
b = i++操作其实是分两步,第一步是先赋值,先将i的值赋给b,第二步i再自增1;b=10;i=11;
b1 = ++i操作也是分两步,第一步是i1先自增1,第二步再赋值,再将i1的值赋值给b1;i1=11;b1=11;
public class Demo{
public static void main(String[] args){
int i = 10;
int b = i--;
System.out.println(b + "," + i);//打印结果为b为10,i为9
int i1 = 10;
int b1 = --i;
System.out.println(b1 + "," + i1);//打印结果为b1为9,i1为9
}
}
b = i--操作其实是分两步,第一步是先赋值,先将i的值赋给b,第二步i再自减1;b=10;i=9;
b1 = --i操作也是分两步,第一步是i1先自减1,第二步再赋值,再将i1的值赋值给b1;i1=9;b1=9;
总结:看++或者--的位置,在前就先++或--,在后就后++或--
public class Demo{
public static void main(String[] args){
int i = 10;
i = i++;
System.out.println(i);//打印i的值为10
}
}
分析下先将i的值10赋给i,i再自增1,那i值因该为11啊,为什么呢?我也不清楚,记住这个就好了,先赋值,再自增,然后自增后的值没有再赋给i,自增了也没用了,只在最开始的时候赋一次值,我是这么理解的。
四、流程控制语句
1.if分支判断
if(表达式),if括号里的表达式的值只能是真或是假,是boolean类型的,为真执行括号里的代码,为假执行else括号里的代码,没有else就跳过。
public class Demo{
public static void main(String[] args){
int score = 99;//定义一个变量score,值为99
if(score >= 90 && score <= 100){ //&&代表且的意思,都为真才执行下面大括号里的代码
System.out.println("优秀");
}
if(score >= 80 && score < 90){
System.out.println("良好");
}
if(score >= 60 && score < 80){
System.out.println("及格");
}
if(score >= 0 && score < 60){
System.out.println("不及格");
}
int num = 10;
if (num == 10)
System.out.printin("num的值为10");//如果if后面不跟大括号,那就只有下面的第一行;前的代码为执行代码
}
}
2.多重if
public class Demo{
public static void main(String[] args){
int score = 99;//定义一个变量score,值为99
if(score >= 90 && score <= 100){
System.out.println("优秀");
}else if(score >= 80 && score < 90){
System.out.println("良好");
}else if(score >= 60 && score < 80){
System.out.println("及格");
}else if(score >= 0 && score < 60){
System.out.println("不及格");
}else{
System.out.println("输入数据不在0-100之间,数据不合理");
}
}
}
3.switch语句
switch(表达式),当程序执行时,根据表达式来去下面case找相匹配的值,找到之后,执行对应的代码,然后执行break;跳出switch语句块。注意不要落下break;语句,不然会穿透执行(看例子)。
switch(表达式){
case 1:
break;
...
}
public class Demo{
public static void main(String[] args){
String s = "春天";//String不是基本数据类型,String是个类,属于引用数据类型
switch (s){
case "春天":
System.out.println("春天真好");
break;
case "夏天":
System.out.println("夏天真好");
break;
case "秋天":
System.out.println("秋天真好");
break;
case "冬天":
System.out.println("冬天真好");
break;
default:
System.out.println("没找到");
break;
}
}
}
例题(1)输入年份,判断是平年还是闰年,闰年能被400整除或者被4整除但不能被100整除。
public class Demo{
public static void main(String[] args){
int year = 2020;
if( year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)){ // %表示取余数,||表示
System.out.println(year + "年是闰年");
}else {
System.out.println(year + "年是平年");
}
}
}
例题(2)输入年份和月份,判断每个月有多少天,闰年2月有29天,平年2月有28天
31:1,3,5,7,8,10,12
30:4,6,9,11
2:闰年29,平年28
public class Demo{
public static void main(String[] args){
int year = 2020;
int month = 2;
int day = 0;
switch (month){
//1,3,5,7,8,10,12月份的执行的语句都是一样,所以可以利用穿透执行,直到break;才结束
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
day = 31;
System.out.println(year + "年" + month + "月" + day + ”天“);
break;
case 4:
case 6:
case 9:
case 11:
day = 30;
System.out.println(year + "年" + month + "月" + day + ”天“);
break;
case 2:
if( year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)){
//闰年
day = 29;
System.out.println(year + "年" + month + "月" + day + "天");
break;
}else {
//平年
day = 28;
System.out.println(year + "年" + month + "月" + day + ”天“);
break;
}
}
}
}
4.三元运算符
public class Demo{
public static void main(String[] args){
int num1 = 1;
int num2 = 2;
int max = num1>num2?num1:num2; //num1>num2的结果为真或假,为真取冒号前的值,为假取冒号后的值
System.out.println(max);
int a = 1;
int b = 2;
int c = 3;
int min = a>b?(b>c?c:b):(a>c?c:a);
System.out.println(min);
}
}
5.while语句--可以一次都不执行
public class Demo{
public static void main(String[] args){
int i = 5;
while(i>0){
System.out.println(i);
i--;
}
}
}
6.do...while语句--至少执行一次
public class Demo{
public static void main(String[] args){
int i = 5;
do{
System.out.println(i);
i--;
}while(i>0);
}
}
7.for循环
public class Demo{
public static void main(String[] args){
for (int i =0;i<10;i++){
System.out.println(i);
}
}
}
例题(3)打印乘法口诀
public class Demo{
public static void main(String[] args){
for (int i = 1; i <=9 ; i++) {//共九行,循环九行
for (int j = 1; j <=i ; j++) {//每一行循环的次数,第一行循环1次,第二行循环2次...,刚好就是i的值
System.out.print(i + "*" + j + "=" + i*j + "\t");
}
System.out.println();
}
}
}
就算是用开发工具也是找到java.exe去执行程序。
例题(4)打印空心菱形
public class Demo{
public static void main(String[] args){
int line=5;//行数
for(int i=1;i<=line;i++){
for(int j=1;j<=line-i;j++){
System.out.print(" ");
}
for(int k=1;k<=2*i-1;k++){
if(k==1||k==2*i-1)//临界点
System.out.print("*");
else
System.out.print(" ");
}
System.out.println();
}
for(int i=line-1;i>=1;i--){
for(int j=1;j<=line-i;j++){
System.out.print(" ");
}
for(int k=1;k<=2*i-1;k++){
if(k==1||k==2*i-1)//临界点
System.out.print("*");
else
System.out.print(" ");
}
System.out.println();
}
}
}
五、函数和数组
1.类
(1)定义
修饰符 class 类名称{
属性
方法
}
2.函数(方法)
(1)定义
修饰符 返回值类型 方法名(参数类型 参数名1,参数类型 参数名2…) {
函数体;
return 返回值;
}
return就结束方法了
(2)方法的重载
方法名相同,但是参数的个数不同,参数的数据类型不同,参数的数据类型顺序不同
public class Demo{
public void method(int a,String b,int c){}
public void method(int a,String b){}
public void method(int a,int c,String b){}
public void method(char c){}
public void method(String b,int a){}
public void method(int c,String b,int a){}//这个是错误的,这个方法的数据类型顺序跟第一个一样,都是int,String,int型的,不构成重载,编译出错
}
例题(1)两种方法求阶乘
/**
求阶乘5!+4!+3!+2!+1!
第一种
5! = 5*4*3*2*1=120
4! = 4*3*2*1=24
3! = 3*2*1=6
2! = 2*1=2
1! = 1=1
*/
public class Demo{
/*
这个类是用来求一个数的阶乘的
*/
public static int method(int num){
int result = 1;
for(int i=num;i>=1;i--){
result = result * i;//累乘
//result *= num;//跟上面的语句一个意思
}
return result;
}
/*
main方法,一个程序的入口,必须这么写,不能改第一行
void表示返回值为空,不需要返回值
在最后也可以加个return;一般不加
*/
public static void main(String[] args){
int total = 0;//定义一个总和变量
for(int i=1;i<=5;i++){
total += method(i);
}
System.out.println(total);
}
}
/**
第二种:递归n! = n*(n-1)!自己调用自己
5! = 5*4!
4! = 4*3!
3! = 3*2!
2! = 2*1!
1! = 1
*/
public class Demo{
/*
这个类是用来求一个数的阶乘的
*/
public static int method(int num){
if(num<1)
return 0;
if(num == 1){
return 1;
}else{
return num*method(num-1);//自己调用自己
}
}
/*
main方法
*/
public static void main(String[] args){
int total = 0;//定义一个总和变量
for(int i=1;i<=5;i++){
total += method(i);
}
System.out.println(total);
}
}
递归:自身调用自身,但必须要有结束标志,意思就是自己调用自己要有一个终点,当num==1的时候,返回1,这就是终点
斐波那契数列:从第三项起每一项的值都是前两项的值的和
1,1,2,3,5,8,13,21,34,55,89,144...
3.数组
数组是用来存一组相同数据类型的集合
(1)定义
三种方法:
int[] arr = new int[5];//[]内指定的是这个数组数据的个数,数组的下标从0开始,这里指定的是5个数,下标最大就是4
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;
arr[3] = 4;
arr[4] = 5;
int[] arr1 = {1,2,3,4,5};//直接初始化
//遍历数组arr1
for(int i=0;i
例题(2)求数组的极值
public class Demo{
public static void main(String[] args) {
int max = 0;
int[] arr = {1,5,3,8,9,6,1,4,3,8,44,65,13};
for (int i = 0; i < arr.length; i++) {
if (arr[i] > max)
max = arr[i];
}
System.out.println(max);
}
}
例题(3)冒泡排序
public class Demo{
public static void main(String[] args) {
int[] arr = {1,6,5,7,8,2,4,9,3};
int temp;
for (int i = 0; i < arr.length-1; i++) {
for (int j = 0; j < arr.length-1-i; j++) {
if (arr[j] > arr[j+1]){
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
for (int i : arr) {
System.out.print(i);
}
}
}
例题(4)数组的倒置
public class Demo{
public static void main(String[] args) {
int[] arr = {1,2,3,4,5};
int temp;
for (int i = 0; i < arr.length/2; i++) {
temp = arr[i];
arr[i] = arr[arr.length-1-i];
arr[arr.length-1-i] = temp;
}
for (int i : arr) {
System.out.print(i);
}
}
}
例题(5)数组合并加排序
public class Demo{
public static void main(String[] args) {
int[] arr = {1,2,3,4,5};
int[] arr1 = {3,5,4};
int[] arr2 = new int[arr.length + arr1.length];
for (int i = 0; i < arr.length; i++) {
arr2[i] = arr[i];
}
for (int i = 0; i < arr1.length; i++) {
arr2[arr.length+i] = arr1[i];
}
int temp;
for (int i = 0; i < arr2.length-1; i++) {
for (int j = 0; j < arr2.length-1-i; j++) {
if (arr2[j] > arr2[j+1]){
temp = arr2[j];
arr2[j] = arr2[j+1];
arr2[j+1] = temp;
}
}
}
for (int i : arr2) {
System.out.print(i);
}
}
}
六、面向对象
1.类的属性和方法
public class User{
int id;//用户id
String username;//用户名称
String password;//用户密码
public void userInfo(){
System.out.println("id:"+id+",username:"+username+",password:"+password);
}
}
id,username,password就是这个类的普通属性(对象属性),这些属性可以不给初始值,他们的初始值为由jvm虚拟机给默认值,userInfo()为普通方法(对象方法)
2.对象
User称为user这个变量的数据类型,不是八大基本数据类型,属于引用类型(类,接口,数组),new User()称为这个类的实例,user称为这个实例的一个引用,每一个实例都有自己的一套属性,不同的对象属性分离,但是这些对象方法是对象共享的。
public class User{
int id;//用户id
String username;//用户名称
String password;//用户密码
public void userInfo(){
System.out.println("id:"+id+",username:"+username+",password:"+password);
}
public static void main(String[] args){
User user = new User();
user.id = 1;
user.username = "小明";
user.password = "123456";
User user1 = new User();
user.id = 2;
user.username = "小花";
user.password = "222222";
user.userInfo();
user1.userInfo();
}
}
内存示意
栈:存储八大基本数据类型和局部变量,先进后出,存储容量小,存取速度快
堆:存储引用数据类型(类的实例,接口,数组),存储容量大,存取速度慢
数据共享区:存储引用数据类型,存储一些共享的方法
所以说user只是一个引用,在栈中存的只是实际对象new User()在堆中的地址而已。new User()这个实际对象存在堆中,每个对象都有同一份属性,如果没赋值,那就由虚拟机看数据类型给默认值,但是对象方法存在数据共享区,每个对象要像使用方法就会去数据共享区调用方法,方法里变量,比如这个userInfo()里的id,username,password的值由对象来给,哪个对象调用这个方法就将对象的属性值赋值上去,所以这种方法叫做对象方法。有时候直接new一个对象,而没有对象的引用,也就没有图中红线的指向,那这个对象很快就会被jvm的gc垃圾回收机制回收,程序结束后,main方法出栈,引用都将不复存在,堆中的对象就会被垃圾回收机制回收掉。
[例]执行顺序
1.在数据区去加载主类.class文件,里面有main方法
2.将main方法入栈,执行主方法
3.执行Dog dog = new Dog();
4.先在数据共享区加载Dog.class文件,解析Dog类
5.在栈中创建局部变量内存区域Dog dog,dog称为引用,new Dog()才是真正的对象,对象存在堆中,假设new Dog()的地址是098
那么dog存的是new Dog()的地址,即dog = 098;
6.在堆中0*98的内存区域创建new Dog()实例的属性,比如有name,age,weight等属性,所以类的属性实际上是存在堆中或者数据共享区
7.执行dog.name="花花";就会在栈中通过引用找到对象的实际地址(在堆中),找到该对象的name属性进行赋值,对象在堆中都是分离开的,意思就是每个对象在堆中都有自己的位置。属性不会共享
8.dog引用调用方法时,就会去数据共享区调用方法,调用方法时不同的对象都是可以调用同一个方法的,所以方法是共享的
局部变量存储在栈中,无默认值,局部变量都是声明在方法中或者代码块中,main方法大括号运行完就消失
类属性存储在堆中,有默认值,由jvm给默认值,属性都是声明在类中,由jvm垃圾回收机制来回收对象
只要dog = null;将引用设为null,dog就不指向堆中的任何对象,这时jvm的gc垃圾回收机制就会销毁对象
3.对象属性的封装
在实际开发中,都会将属性进行封装,封装就是在属性前加private修饰符,这样属性就只能在这个类中使用。然后提供每个属性的get和set方法。
public class Person{
private int id;
private String username;
private String password;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
4.this关键字
this就是表示一个对象引用,哪个对象调用这个对象方法,哪个对象就是这个this。如果形参和下面属性同名,this.就必须要写出来来区分是形参还是对象属性
public class Demo{
public static void main(String[] args){
User user = new User();
user.setId(1);
user.setUsername("小明");
user.setPassword("123456");
}
}
this就是对象的引用,哪个对象调用对象方法,哪个对象就是this
5.构造器(构造方法):用来做初始化的
public class Person{
private int id;
private String username;
private String password;
//显示的声明这个无参构造器,有时候类里没写默认的无参构造器,那就由这个类默认提供一个无参构造器
public Person(){
System.out.println("我是默认的构造方法");
}
//重载方法,用来做初始化
public Person(int id,String username,String password){
this.id = id;
this.username = username;
this.password = password;
}
public static void main(String[] args){
Person person = new Person();//new 一个对象的时候实际上就是调用构造方法;这个实例对象调用无参构造器
Person person1 = new Person(1,"小花","123456");//这个方法调用有参构造器,这样就给对象的属性赋上了值
//Person代表类,person,person1代表个体,就像人是一个类,每个独立的个体是一个实例对象
}
}
6.static关键字
在学习前面的知识的时候,总是能看见static关键字,那这个static到底是干嘛的呢?
static修饰在属性上时,这个属性从此以后就属于这个类了,所有对象在被new的时候就不会将这个属性复制一份到自己的内存里,static修饰的属性和方法都是存在数据共享区,只有一份。
static修饰在方法上时,那这个方法就是类方法,请注意区别于对象方法。
这些属性和方法在被调用的时候,就可以直接用用类名.属性或者类名.方法来进行调用即可,如果是在自己类里调用,类名.可以省略不写。也可以使用这个类的每一个对象调用都是可以的。
public class Test{
static{
System.out.println("我是一个静态代码块,我是用来做一些数据的初始化的,当加载这个类的时候我是第一个被调用的");
}
{
System.out.println("我是一个代码块,我是在实例化对象的时候被调用,我比静态代码块慢,但是比构造方法快");
}
public Test(){
System.out.println("我是构造方法");
}
private static final double PI = 3.14;
public static void fn(){
System.out.println("这是一个静态方法");
}
public static void main(String[] args) {
Test test = new Test();
System.out.println(test.PI);//对象调用
test.fn();//对象调用
System.out.println(Test.PI);//类调用
Test.fn();//建议用类来调用静态方法,就不用创建对象来调用,能够节省内存
}
}
注意:static修饰的属性和方法都只有一份,都保存在数据共享区里,类被加载的时候就会先执行静态的属性和方法,再加载对象方法进数据共享区,对象属性只有在实例化对象的时候才会在堆中创建,对象的引用在栈中创建,存的是实际对象在堆中的地址。
7.设计模式之单例模式
/**
懒汉式单例:不访问就不创建对象,当一个去调用这个方法的时候才去创建对象
单例:唯一一个对象,就想到用static去修饰对象,在数据共享区去创建唯一对象
将构造方法设置为私有的,类外部就不能去实例化对象了,private修饰的只能在该类中使用
好处:节省内存
*/
public class Singleton{
private static Singleton singleton = null;//如果不想在外部被直接用Singleton.singleton来获取单例,可以设为private,只能在该类中使用Singleton.singleton
private Singleton(){}
/*
要提供一个能给外部获取这个单例的方法,不然这个单例创建了有什么用了?
在别的类中使用该类的方法就得直接用类名.方法来调用,因为外部已经不能创建对象了,只能用类名来调用
*/
public static Singleton getInstance(){
if(singleton == null){//一开始为null
singleton = new Singleton();//当这个方法第一次被调用时,创建单例
return singleton;//返回单例
}else{//第二次及以上上,这个singleton在数据共享区已经不为null了,
return singleton;//返回第一次创建号的单例
}
}
}
/**
饿汉式单例:来访问就直接给单例
*/
public class Singleton{
private static Singleton singleton = new Singleton();
private Singleton(){}
/*
要提供一个能给外部获取这个单例的方法,不然这个单例创建了有什么用了?
在别的类中使用该类的方法就得直接用类名.方法来调用,因为外部已经不能创建对象了,只能用类名来调用
*/
public static Singleton getInstance(){
return singleton;//直接返回单例
}
}
注意:每一个.java文件可以有多个类,但是只能有一个类可以被public修饰,表示该类为主类,但是所有类都可以用main方法,比如下面的Singleton也可以有一个main方法。
public class Test{
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
System.out.println(singleton);
}
}
class Singleton{
private static Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return singleton;//直接返回单例
}
}
七、类的继承
1.java中类只能单继承,用关键字extends表示继承,继承父类的属性和方法,但是父类中private修饰的不能继承,因为private修饰的只能在该类中使用
class A{
int id;
String username;
}
class B extends A{
String password;
private String email;
}
public class C extends B{
public static void main(String[] args){
C c = new C();
c.id = 1;
c.username = "小明";
c.password = "123456";
//c对象不能访问email属性,因为是private修饰的
}
}
2.不能多继承的原因
类A和类B都有同一个一个方法fu,那在继承的时候到底继承谁的方法,所以java语法中不允许多继承,但是可以像上面那样,B继承A,C继承B。
class A{
public void fu(){
System.out.println("A");
}
}
class B{
public void fu(){
System.out.println("B");
}
}
public class C extends A,B{
public static void main(String[] args){
}
}
3.父类中的私有属性继承不到怎么办?在父类中提供get和set方法
class A{
int id;
String username;
}
class B extends A{
String password;
private String email;
public String getEmail(){
return email;
}
public void setEmail(String email){
this.email = email;
}
}
public class C extends B{
public static void main(String[] args){
C c = new C();
c.id = 1;
c.username = "小明";
c.password = "123456";
//c对象不能访问email属性,因为是private修饰的
c.setEmail("[email protected]");
System.out.println(c.getEmail());
}
}
4.super关键字的使用
super表示对父类的一个引用;
super();表示对父类无参构造函数的一个引用,调用的是无参构造器,如果父类有其他的构造器就会覆盖默认的构造器,所以就要显示的定义无参构造器;
super(...);表示对父类有参构造函数的一个引用;
class A{
int id;
String username;
public A(){}
public A(int id, String username) {
this.id = id;
this.username = username;
}
}
class B extends A{
String password;
public B(){//显示的无参构造器
super();//可不写,要写的话必须卸载第一行
}
public B(int id, String username, String password) {//如果定义了其他的有参构造器,就会覆盖默认的无参构造器,所以要现实的写出无参构造器
super(id, username);
this.password = password;
}
}
public class C extends B{
public C(){
super();//可不写,不写时是默认存在的,但是父类的无参构造器必须有
}
public C(int id, String username, String password) {
super(id, username, password);
}
public static void main(String[] args){
C c = new C(1,"小明","123456");
}
}
注意:
1.子类通过默认构造器实例化的过程中父类的构造器先被调用,然后再调用子类的构造器,在子类构造器内部默认的调用super();
2.如果子类的构造器中调用了父类中的有参数的构造器,默认super();的调用就不会有了,不能再子类的构造器中调用两个super(...);
3.super(...);的调用必须要放在方法的第一行
4.super可以表示父类的引用,我们可以使用super和this来区分父类和子类中同名的属性
重点:
1.理解super(...);对于父类构造器的调用
2.super表示子类内部的那个父类的对象的引用。在实例化子类对象的时候会先创建父类的实例对象,在调用属性和方法时,先在子类实例的内存中去找对应的属性和方法,如果没找到,就会去父类的实例对象中去找属性和方法。
This关键字:
1.代表当前类的指定实例的引用
2.可以区分同名的属性和局部变量
3.通过this可以调用同类中的构造器(this,this(参数列表))
3.调用本类里面的属性,this.属性名,this.方法()
5.方法的重写
在子类继承父类时子类的方法和父类的方法相同(访问修饰符,返回值类型,方法名,参数列表),方法体不同。这种子类的方法将父类;的方法覆盖叫做重写。
重写和重载的区别:
重写(override):在父子类的继承中有相同的方法,唯一不同就是方法体,一般是父类的该方法满足不了子类的需求所以才放生重写。
重载(overload):重载是在同一个类中,有着相同的方法名但是参数的数据类型或者参数的个数不同这两个方法就是重载。重载的目的:节省类中的命名资源和提高代码的可读性。
6.final关键字
final修饰变量时,这个变量就变成了一个常量
final修饰属性时,在使用这个属性前要进行一次初始化,初始化后就不能再修改值,可以用构造方法进行初始化,这个属性同样也是属于每一个对象的,每个对象都会有自己的这个属性,每个对象都能对这个属性进行一次初始化,要想将这个属性变成唯一的,可以用static修饰,此时这个属性就是唯一的,并且不能修改值
final修饰方法时,表示这个方法不能被重写,final表示最终
final修饰类时,表示这个类不能被继承
public class Test{
public static final double PI = 3.14;
public static void main(String[] args) {
System.out.println(Test.PI);//最好直接用类名.属性名,不用实例化对象,节约内存
Test test = new Test();
System.out.println(test.PI);
}
}
8、抽象类和接口
1.抽象类(extends)
(1)定义
抽象类:当多个具体的实体类存在着共同的行为,但是有不同的表现,在父类继承过程中父类的方法具体实现不确定,但是能确定的是他们都有这种行为。
我们把这种行为方法称为抽象方法
abstract class 类名{
//属性
//方法
public abstract void 方法名();//抽象方法没有方法体
}
(2)注意
1.抽象类不一定有抽象方法,但是有抽象方法的类一定是抽象类
2.抽象类不能被实例化,只能被继承,然后实例化子类
3.继承了抽象类的非抽象类必须实现抽象类的所有方法
4.抽象类也能继承抽象类,这样就不用实现父类的抽象方法
5.抽象类的抽象方法不能和private(private修饰的方法不能被子类访问到,就不能被子类实现) final(final修饰的方法不能被重写) static(抽象方法是对象方法,不能是类方法)共存。
2.接口(implements)
接口支持多继承,类不支持多继承
(1)定义
接口里的属性全是常量
接口里的方法全是抽象方法,没有方法体
jdk1.8后接口里可以有默认方法,默认方法可以有方法体,当子类不实现这个默认方法时,调用这个方法就执行默认方法体
public interface A{
public abstract final double PI = 3.14;
public abstract void fn();//
public default void fn2(){
System.out.println("默认方法");
}
}
public interface A{
public static final double CIRCLE_PI = 3.14;
public abstract method();
}
public interface B{
public abstract method();
}
public interface C extends A,B{
}
(2)注意
1.接口C继承接口A和接口B,并且两个方法名相同,但是接口里的方法都是抽象方法,抽象方法是没有方法体的,没有被具体定义的,不同于类的方法,所以接口继承接口的时候,相同的方法名会认为是同一个,也就可以多继承,在实现就接口的时候要实现所有的接口里的方法,不然报错,同名的也只实现一次
2.定义常量,接口里的“属性”全都是常量,前面有static和final修饰的,public static final可以省略不写,但是默认是有的,我们要知道这里实际是省略了public static final的,常量的定义由XXX_YY命名法,字母全部大写,多个单词用_分割,并且不能修改常量的值
9、对象的多态
(1)子类转父类 (自动转换) 父类是抽象类同样符合
子类提升为父类,调用的是父类的东西,不能调用子类的东西,除非子类对父类的方法进行重写了,那就调用子类的重写方法,但是父类与子类有同名属性的话,还是调用父类的属性,不要和重写的方法混淆,没重写的方法还是用父类的
interface Teacher{
public abstract void teach();
}
class JavaTeacher implements Teacher{
String name;
@Override
public void teach() {
System.out.println(this.name + "在教Java课");
}
}
class PhpTeacher implements Teacher{
String name;
@Override
public void teach() {
System.out.println(this.name + "在教PHP课");
}
}
class Leader{
public void check(Teacher teacher){
if (teacher instanceof JavaTeacher){
System.out.println("Java老师");
}
if (teacher instanceof PhpTeacher){
System.out.println("Php老师");
}
System.out.println("开始上课");
teacher.teach();
System.out.println("开始打分");
}
}
public class Demo5 {
public static void main(String[] args) {
JavaTeacher jt = new JavaTeacher();
jt.name = "大哥";
PhpTeacher pt = new PhpTeacher();
pt.name = "二哥";
Leader leader = new Leader();
leader.check(jt);
System.out.println("=========");
leader.check(pt);
}
}
(2)父类转子类(强制转换) 父类是抽象类同样符合
abstract class Teacher{
public abstract void teach();
}
class JavaTeacher extends Teacher{
String name;
int age;
@Override
public void teach() {
System.out.println(this.name + "在教Java课");
}
}
class PhpTeacher extends Teacher{
String name;
@Override
public void teach() {
System.out.println(this.name + "在教PHP课");
}
}
class Leader{
public void check(Teacher teacher){
if (teacher instanceof JavaTeacher){
System.out.println("Java老师");
}
if (teacher instanceof PhpTeacher){
System.out.println("Php老师");
}
System.out.println("开始上课");
teacher.teach();
System.out.println("开始打分");
}
}
public class Demo5 {
public static void main(String[] args) {
Teacher teacher = new JavaTeacher();
JavaTeacher jt = (JavaTeacher)teacher;
System.out.println(jt.age);//0
jt.age = 22;
System.out.println(jt.age);//22
}
}
既然父类的引用指向的是子类的实例,且这个引用不能访问子类的特有属性和方法,那要怎么访问呢
这样解决:将这个父类的引用强转为子类的引用就可以让转化后的引用访问自己的属性和方法了
强转的前提是父类的真身是子类实例
下面这是不可以的,因为父类的真身不是子类的实例对象,尽管JavaTeacher是Teacher的子类,但是父类也不能直接强转为子类,同为子类的实例也不能互相转
Teacher t = new Teacher();
JavaTeacher jt = (JavaTeacher)t;//不可以
JavaTeacher jt = new JavaTeacher();
PhpTeacher pt = (PhpTeacher)jt;//不可以
注意:(只限于Object类)
Object类是所有类的父类,是个万能的类,它可以强转为所有子类,注意区别于上面
Object类型的对象可以转化为任意类型的对象
10、扫描器
public class Test{
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int num = scanner.nextInt();
if (num == 23){
System.out.println("猜对了");
}else if (num < 23){
System.out.println("小了");
}else {
System.out.println("大了");
}
String str = scanner.nextLine();
System.out.println(str);
}
}