Java学习笔记
文章目录
1.环境搭建
安装JDK
因为Java程序必须运行在JVM之上,所以,第一件事情就是安装JDK。
搜索JDK 13,确保从Oracle的官网下载最新的稳定版JDK:
设置环境变量
安装完JDK后,需要设置一个JAVA_HOME
的环境变量,它指向JDK的安装目录。在Windows下,它是安装目录,类似:
C:\Program Files\Java\jdk-13
在Mac下,它在~/.bash_profile
里,它是:
export JAVA_HOME=`/usr/libexec/java_home -v 13`
然后,把JAVA_HOME
的bin
目录附加到系统环境变量PATH
上。在Windows下,它长这样:
Path=%JAVA_HOME%\bin;<现有的其他路径>
在Mac下,它在~/.bash_profile
里,长这样
export PATH=$JAVA_HOME/bin:$PATH
把JAVA_HOME
的bin
目录添加到PATH
中是为了在任意文件夹下都可以运行java
。打开命令提示符窗口,输入命令java -version
,如果一切正常,你会看到如下输出:
┌────────────────────────────────────────────────────────┐
│Command Prompt - □ x │
├────────────────────────────────────────────────────────┤
│Microsoft Windows [Version 10.0.0] │
│(c) 2015 Microsoft Corporation. All rights reserved. │
│ │
│C:\> java -version │
│java version "13" ... │
│Java(TM) SE Runtime Environment │
│Java HotSpot(TM) 64-Bit Server VM │
│ │
│C:\> │
│ │
│ │
└────────────────────────────────────────────────────────┘
如果你看到的版本号不是13
,而是12
、1.8
之类,说明系统存在多个JDK,且默认JDK不是JDK 13,需要把JDK 13提到PATH
前面。
如果你得到一个错误输出:
┌────────────────────────────────────────────────────────┐
│Command Prompt - □ x │
├────────────────────────────────────────────────────────┤
│Microsoft Windows [Version 10.0.0] │
│(c) 2015 Microsoft Corporation. All rights reserved. │
│ │
│C:\> java -version │
│'java' is not recognized as an internal or external comm│
│and, operable program or batch file. │
│ │
│C:\> │
│ │
│ │
│ │
└────────────────────────────────────────────────────────┘
2.Java基础语法
简要介绍下类、对象、方法和实例的概念。
- 对象:对象是类的一个实例,有一个对象,有一个对象,它的有:颜色、名字、方法;行为有:摇尾巴、叫声、吃等。
- 类:类是一个模板,它描述对象的行为和状态。
- 方法:方法就是行为,一个类可以有很多方法。逻辑运算、数据修改以及所有动作方法都在中完成的。
- 实例变量:每个变量对象存在独特的实例,对象的状态由这些实例变量的值决定。
第一个程序HelloWorld
/**
* 可以用来自动创建文档的注释
*/
public class Hello {
public static void main(String[] args) {
// 向屏幕输出文本:
System.out.println("Hello, world!");
/* 多行注释开始
注释内容
注释结束 */
}
} // class定义结束
变量和数据类型
按所属的数据类型划分:基本类型的变量和引用类型的变量。
按被声明的位置划分:局部变量(方法或语句块内部定义的变量)和成员变量(方法外部、类的内部定义的变量)
基本类型的变量
在Java中,变量必须先定义后使用,在定义变量的时候,可以给它一个初始值。例如:
int x = 1;
基本数据类型
基本数据类型是CPU可以直接进行运算的类型。Java定义了以下几种基本数据类型:
- 整数类型:byte,short,int,long
- 浮点数类型:float,double
- 字符类型:char
- 布尔类型:boolean
基本数据类型转换:
- 容量小的类型自动转换成容量大的类型
- byte,short,int,它们三者再计算时会转换成int类型
- 如果把int值转换成float值,或者long转换成double值,不需要强制转换,但可能丢失精度
强制类型转换:
- 容量大的类型转换成容量小的数据类型时,要加上强制转换符
方法与数组
方法的定义:
方法(又叫函数)就是一段特定功能的代码块。方法提高程序的复用性和可读性。
方法的格式
// 语法:
// 访问权限修饰符[其他的修饰符如static]返回值类型 方法名(参数类型1形参1,参数类型2形参2,...){// 形参列表
// 方法体
return 返回值;
}
参数:
实际参数:就是实际参与运算的。
形式参数:就是方法定义上的,用于接受实际参数的;
参数类型:就是参数的数据类型
参数名:就是变量名
方法体语句:就是完成功能的代码
注意:
1.若当前方法中不要使用形参,那么形参列表可以为空
2.实参和形参的类型要相互兼容,且:实参的取值范围要小于或者等于形参类型的取值范围
在调用方法中,如果我们定义的方法有参数,就必须在调用方法的同时传入这个值,即给当前方法中的参数赋值,而这个传入的值我们称为实记参数,也就是实参
实参:传入的参数值
形参:接受实参传过来的值
注意:实参名与形参名可以相同,也可以不同
小结:形参就是一个变量,实参就是一个值,传参就是把一个值给一个形参赋值
方法的返回值
return: 结束方法的
返回值:就是功恩的结果,由return带给调用者。
注意:
- 若当前方法没有返回值类型,即返回值类型是void,那么当前方法中可以不写return
- return即表示结束一个方法,也可以将返回值返回给调用当前方法的调用者
- return返回值时一次只能返回一个值,不可以返回多个值
- 一个方法中可以有多个return,但被执行的只能有一个,所以需要判断
练习:
-
判断任意给定年份是否是闰年
public class test { public static void main(String[] args){ boolean bool = isRunNian(2017); if(bool){ System.out.println("是闰年"); }else{ System.out.println("是平年"); } } public static boolean isRunNian(int year){ if((year%4==0 && year%100!=0) || year%400==0){ return true; }else{ return false; } } }
-
根据传入的参数数量打印直角三角形
public class printTriangle { public static void main (String[] args){ Scanner input = new Scanner(System.in); int num = input.nextInt(); print(num); } public static void print(int line) { for(int i=1 ; i<=line ; i++) { // 控制行数 for(int j=i;j<line;j++) { System.out.print(" "); } for(int k=1; k<=i*2-1; k++) { System.out.print("*"); } System.out.println(); } } }
方法的重载
方法重载:overloading method
在类中可以创建多个方法,它们具有相同的名字,但具有不同的参数和不同的定义;
返回值不能作为重载的条件
// 如:
public void method(int a){...}
public void method(char b){...}
数组的定义
数组:一组能够存储相同数据类型值的变量的集合
当我们有个一组相同类型的数据需要存储,如果次是使用单个变量的来存储,我们将要定义若干个变量名,这样将会非常繁琐,并不利于维护
数组的赋值方式
四种:
-
使用默认的初始值来初始化数组中的每一个数组
语法:数组元素类型 [] 数组名 = new 数组元素类型[数组中元素的个数(数组的长度)]
如:int [] scores = new int[3];
-
先声明,然后再赋予默认的初始值
语法:数组元素类型 [] 数组名;
数组名 = new 数组元素类型[数组中元素的个数(数组的长度)];
如:int [] scores;
scores = new int[3];
-
先声明,然后在使用特定的值进行初始化
语法:数组元素类型 [] 数组名 = new 数组元素类型 [] {元素1,元素2,…};
如:int [] scores = new int[]{56,78,98};
-
将第三种写法可以简化为(使用数组常量值给数组进行赋值)
语法:数组袁术类型 [] 数组名 = {元素1,元素2,…};
如:int [] scores = {56,78,98}
数组的遍历
遍历:依次语出数组中的每一个元素
通过下标来访问数组的元素
下标: 从0开始,到数组长度-1
-
遍历方法一:普通的for循环
语法:for(int i = 0; i< 数组的长度; i++){
// i: 循环变量,同样:也是数组的下标(取值范围[0,数组的长度])
数组中元素的类型 变量 = 数组名[i];
}
-
遍历方式二:使用增强for循环【foreach循环】
语法:for(数组中元素的类型 变量:数组名){
数组中元素的类型 临时变量 = 变量;
}
数组示例
-
猜数游戏,从键盘中任意输入一个数据,判断数列中是否包含此数
public class test2 { // 猜数游戏,从键盘中任意输入一个数据,判断数列中是否包含此数 public static void main(String[] args) { int[] nums = {2,32,23,54,60}; Scanner input = new Scanner(System.in); System.out.println("请输入你要猜的数:(60以内)"); int userNum = input.nextInt(); boolean flag = false; for(int i:nums){ if(userNum == i){ flag = true; break; } } if(flag) { System.out.println("恭喜你猜对了"); }else{ System.out.println("猜错了继续努力"); } } }
-
打印正三角形
多维数组示例:
1.一起来参加屌丝程序员大赛把,有3个班级各3名学员参赛,记录每个学员的成绩,并计算每个班的平均分。
public class test3 {
public static void main(String[] args){
int [][] scores = {{99,78,50},{90,86},{67,87,56}};
int classLen = scores.length;
for(int[] i:scores){
int sum = 0;
for(int j:i){
sum += j;
}
int avg = sum / i.length;
System.out.println("平均成绩:"+avg);
}
}
}
最大值最小值算法
求最大值与最小值的算法
最大值:在一个数列中找出最大的数
public class test4 {
public static void main(String[] args){
int[] nums = {1,2,3,4,5,6};
int max = max(nums);
System.out.println(max);
}
public static int max(int [] nums){
int max = nums[0];
int len = nums.length;
for(int i = 1;i<len;i++){
if(max < nums[i]){
nums[i] = nums[i] + max;
max = nums[i] - max;
nums[i] = nums[i] - max;
}
}
return max;
}
}
最小值:在一个数列中找出最小的数
public class test4 {
public static void main(String[] args){
int[] nums = {1,2,3,4,5,6};
int min = min(nums);
System.out.println(min);
}
public static int max(int [] nums){
int min = nums[0];
int len = nums.length;
for(int i = 1;i<len;i++){
if(min > nums[i]){
nums[i] = nums[i] + min;
min = nums[i] - max;
nums[i] = nums[i] - min;
}
}
return min;
}
}
冒泡排序算法
冒泡排序算法
冒泡排序算法的运作如下:(从后往前)
比较相邻的元素。如果第一个比第二个大,就交换他们两个。
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,知道没有任何一对数字需要比较。
相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法
public class test5 {
public static void main(String[] args){
int[] nums = {10,21,3,34,5,19};
int len = nums.length-1;
for(int i = 0; i < len;i++){
for(int j = 0; j < len-i;j++){
if(nums[j] > nums[j+1]){
nums[j+1] = nums[j]+nums[j+1];
nums[j] = nums[j+1] - nums[j];
nums[j+1] = nums[j+1] - nums[j];
}
}
}
for(int n:nums){
System.out.println(n);
}
}
}
选择排序算法
每一趟从待排序的数据元素中选出最小(或最大)的一个元素,顺序放在已排好序的数列的最后,直到全部待排序的数据元素排完。选择排序是不稳定的排序方法
public class test6 {
public static void main(String[] args){
int[] nums = {10,21,3,34,5,19};
int len = nums.length;
int minIndex;
for(int i=0;i<len;i++){
minIndex = i;
for(int j=i+1;j<len-1;j++){
if(nums[minIndex] > nums[j]){
minIndex = j;
}
}
if(minIndex != i){
if(nums[i] > nums[minIndex]){
nums[minIndex] = nums[i]+nums[minIndex];
nums[i] = nums[minIndex] - nums[i];
nums[minIndex] = nums[minIndex] - nums[i];
}
}
}
for(int n:nums){
System.out.println(n);
}
}
}
直接插入排序算法
直接插入排序算法
(从后向前找到合适位置后插入)
**基本思想:**每步将一个待排序的记录,按其顺序码大小插入到前面已经排好序的子序列的合适位置(从后向前找到合适位置后),直到全部插入排序完为止。
public class test7 {
public static void main(String[] args){
int[] nums = {10,21,3,34,5,19};
for(int i = 1;i<nums.length;i++){
int temp = nums[i];
int j;
for(j = i-1;j>=0;j--){
if(nums[j] > temp){
nums[j+1] = nums[j];
}else{
break;
}
}
if(temp != nums[j+1]){
nums[j+1] = temp;
}
}
for(int n:nums){
System.out.println(n);
}
}
}
二分查找算法
二分法查找(折半查找):前提是在已经排好序的数组中,通过将待查找的元素与中间索引值对应的元素进行比较,若大于中间索引值对应的元素,去右半部分查找,否则,去左半部分查找。依次类推。知道找到为止,找不到返回一个负数。
public class test8 {
public static void main(String[] args){
int[] nums = {1,2,3,4,5,6};
int index = binarySearch(nums,3);
System.out.println(index);
}
public static int binarySearch(int[] nums, int key){
int start = 0;
int end = nums.length-1;
while (start<=end){
int middle = (start+end)/2;
if(nums[middle]>key){
end = middle-1;
}else if(nums[middle]<key){
start = middle+1;
}else{
return middle;
}
}
return -1;
}
}
Arrays类
Arrays工具类:用来操作数组(比如排序和搜索)的各种方法
常用方法:
-
使用二分法查找
Arrays.binarySearch(int [] array,int value);
-
数组内容转成字符串的形式输出
Arrays.toString(int [] array);
-
数组排序
Arrays.sort(int[] array);
-
复制指定的数组
Arrays.copyOf(int[] array,int length)
Arrays.copyOf(int[] array,int form,int to)
System.arraycopy(Object src, int s rcPos,Object dest,int destPos,int length)
-
判断两个数组是否相等
Arrays.equels();
-
使用指定元素填充数组
Arrays.fill()
双色球模拟综合案例
一、双色球彩票玩法
玩法说明:
双色求投注区分为红色号码区和蓝球号码区,红球号码范围为0133,蓝球号码范围为0116。双色球每期从33个红球中开出6个号码,从16个蓝球中 开出1个号码作为中将号码,双色球玩法即是竞猜开奖号码的6个红球号码和1个蓝球号码,顺序不限
二、案例分析
1、如何产生蓝球和红球?
2、如何接受用户选号?
3、如何验证是否中奖?
4、公布本期中将号码?
三、实现步骤:
1、整体实现思路
2、随机取值不重复算法(系统和用户)
3、判断是否中奖的逻辑
4、结果输出
import java.util.Arrays;
import java.util.Random;
import java.util.Scanner;
public class test9 {
public static void main(String[] args){
// 定义相关变量
int[] userRedBall = new int[6];// 用户选择的红球号码
int[] sysRedBall = new int[6];// 系统生成的红球号码
int userBlueBall = 0;// 用户选择的蓝球号码
int sysBlueBall = 0;// 系统生成的蓝球号码
int redCount = 0;// 记录用户选择正确的红球
int blueCount = 0;// 记录用户选择正确的蓝球
int [] redBall = new int[33]; //用于存储1-33的红球号码
for(int i = 0;i<redBall.length;i++){
redBall[i] = i+1;
}
// 游戏开始提示
System.out.println("双色球游戏开始,good luck");
System.out.println("请问你是要机选还是要手选:(1:机选,2:首选)");
Scanner input = new Scanner(System.in);
Random r = new Random();
boolean flag = true;
while(flag){
int isAuto = input.nextInt();
switch (isAuto){
case 1:
// 机选
computerSelect(redBall,userRedBall);
userBlueBall = r.nextInt(16)+1;
flag = false;
break;
case 2:
// 手选
System.out.println("请选择6个红球号码(1-33)");
for(int i=0;i<userRedBall.length;i++){
userRedBall[i] = input.nextInt();
}
System.out.println("请选择1个蓝球号码(1-16)");
userBlueBall = input.nextInt();
flag = false;
break;
default:
System.out.println("请问你是要机选还是要手选:(1:机选,2:首选)");
break;
}
}
// 系统随机生成号码
computerSelect(redBall,sysRedBall);
sysBlueBall = r.nextInt(16)+1;
for(int i=0;i<userRedBall.length;i++){
for(int j=0;j<sysRedBall.length-redCount;j++){
if(userRedBall[i] == sysRedBall[j]){
int temp = userRedBall[i];
userRedBall[i] = sysRedBall[userRedBall.length-1-redCount];
sysRedBall[userRedBall.length-1-redCount] = temp;
redCount++;
break;
}
}
}
if(userBlueBall == sysBlueBall){
blueCount = 1;
}
if(blueCount==0 && redCount<=3){
System.out.println("没中奖");
}else if(blueCount==1 && redCount<3){
System.out.println("中了六等级,5块钱");
}else if(blueCount==1 && redCount==3 || blueCount==0 && redCount==4){
System.out.println("中了五等级,10块钱");
}else if(blueCount==1 && redCount==4 || blueCount==0 && redCount==5){
System.out.println("中了四等级,200块钱");
}else if(blueCount==1 && redCount==5){
System.out.println("中了三等级,3000块钱");
}else if(blueCount==0 && redCount==6){
System.out.println("中了三等级,150w块钱");
}else if(blueCount==1 && redCount==6){
System.out.println("中了三等级,500w块钱");
}else{
System.out.println("系统有误,中奖无效");
}
// 系统号码
System.out.println("本期中奖的红球");
Arrays.sort(sysRedBall);
System.out.println(Arrays.toString(sysRedBall));
System.out.println("本期中奖的蓝球");
System.out.println(sysBlueBall);
// 用户号码
System.out.println("你选择的红球");
Arrays.sort(userRedBall);
System.out.println(Arrays.toString(userRedBall));
System.out.println("你选择的蓝球");
System.out.println(userBlueBall);
}
public static void computerSelect(int[] redBall,int[] userRedBall){
Random r = new Random();
int index = -1;
for(int i=0;i<userRedBall.length;i++){
index = r.nextInt(redBall.length-i);
userRedBall[i] = redBall[index];
//交换位置
int temp = redBall[index];
redBall[index] = redBall[redBall.length-1-i];
redBall[redBall.length-1-i] = temp;
}
}
}
3.面向对象基本概念
一、什么是面向对象:
1.面向对象是一种编程思想。
2.面向对象是一种思考问题的思维方式。
二、简历面向对象思维方式:
1.先整体,再局部
2.先抽象,再具体
3.能做什么,再怎么作
三、如何学习面向对象
1.掌握一门面向对象语言的语法
2.熟悉面向对象的设计原则
3.熟悉面向对象设计模式
类与对象
- 类是:分类、类别
- 通过分类,我们可以区别不同的事物种类,在日常生活当中,我们唱常这么做。
- 所以,类是一组具有相同特性(属性)与行为(方法)的事物集合。
类与对象的关系
-
类便是一个共性的产物,是一个综合的特征,而对象,是一个个性的产物,是一个个体的特征。
-
类由属性和方法组成
属性:就相当于一个个的特征
方法:就相当于人的一个个的行为,例如:说话,吃饭,唱歌,睡觉
类和对象的定义格式
在Java中可以使用一下的语句定义一个类:
class 类名称{
属性名称;
返回值类型 方法名称(){}
}
对象的定义:
一个类要想真正的进行操作,则必须依靠对象,对象的定义格式如下:
类名称 对象名称 = new 类名称();
按照以上的格式就可以产生对象了。
如果要想访问类中的属性或方法(方法的定义)
则可以依靠一下的语法形式:
访问类中的属性:
对象.属性;
调用类中的方法:
对象.方法();
对象的内存分析
- new 关键字表示创建一个对象
- new 关键字表示实例化对象
- new 关键字表示申请内存空间
注意:如果一个没有申请内存空间的对象,会报空指针异常
类与对象小结
- new 关键字:表示向内存申请空间,也表示实例化一个对象,创建一个对象。
- 一个对象在内存中的大小,由该对象的所有属性所占的内存大小的总和。引用类型变量在32位系统上占4个字节,在64位系统上占8个字节。加上而歪的对象隐性数据所占的大小。
- 相同的类型才可以赋值
- 不同的引用,指向同一个对象,任何一个引用改变对象的值,其他引用都会反映出来。
- 编程时要注意的问题,在确定不使用对象时,要尽早释放对象:引用=null
- 当一个推中的对象没有被任何引用变量所指向时,该对象会被JVM的GC程序认为是垃圾对象,从而被回收
封装性
一、封装性的概念
-
封装性是面向对象思想的三大特征之一。
-
封装就是隐藏实现细节,仅对外提供访问接口。
封装有:属性的封装、方法的封装、类的封装、组件的封装、模块化的封装、系统级封装…
二、封装的好处
-
模块化
-
信息隐藏
-
代码复用
-
插件化易于调试
-
具有安全性
封装的缺点:会影响执行效率
private关键字:访问权限修饰符,public表示共有的,private表示私有的,私有的属性或方法,只能在本类中访问
puclick class Test10{
public static void main (String() args){
}
}
class Person{
private String name;
private int age;
public void setName(String name){
this.name = name;
}
public String getName(){
return this.name;
}
}
成员变量和局部变量
-
在类中的位置不同
成员变量:在类中定义
局部变量:在方法中定义或者方法的参数
-
在内存中的位置不同
成员变量:在堆内存(成员变量属于对象,对象进堆内存)
局部变量:在栈内存(局部变量属于方法,方法进占内存)
-
生命周期不同
成员变量:随着对象的创建而存在,随着对象的销毁而消失
局部变量:随着方法的调用而存在,随着方法的调用完毕而消失
-
初始化值不同
成员变量:有默认初始化值,引用类型为null局部变量:没有默认初始化值,必须定义,赋值,然后才能使用
注意:局部变量名称可以和成员变量名称一样,在方法中使用的时候,采用的是就近原则
构造方法
什么是构造方法
- 构造方法就是类构造对象时调用的方法,用于对象的初始化工作
- 构造方法是实例化一个类的对象时,也就是new的时候,最先调用的方法
构造方法的定义:
构造方法是在类型定义的,构造方法的定义格式:方法名称与类的名称相同,无返回值类型的声明。
对象的实例化语法:
Dog dog = new Dog();// new Dog后面有个括号,表示调用了方法,此时调用的方法就是构造方法了。
class Dog{
public Dog(){
System.out.printLn("构造方法执行了")
}
public Dog(String name,int age){
this.name = name;
this.age = age;
System.out.printLn("带两个参数的构造方法执行了")
}
}
构造方法小结
- 构造方法名称与类名相同,没有返回值声明(包括void)
- 构造方法用域初始化数据(属性)
- 每一个类中都会含有一个默认的午餐的构造方法
- 如果类中有显示的构造方法,那么默认构造方法将无效
- 如果有显示的构造方法,还想保留默认构造方法,需要显示的写出来
- 构造方法可以有多个,但参数不一样,成为构造方法的重载
- 在构造方法中调用另一个构造方法,使用this(…),该句代码必须在第一句
- 构造方法之间的调用用,必须要有出口。
- 给对象初始化数据可以使用构造方法或setter方法,通常情况下,两者都会保留
- 一个好的编程习惯是要保留默认的构造方法。(为了方便一些框架代码使用反射创建对象)
- private Dog(){},构造方法私有化,当我们的需求是为了保证该类只有一个对象时,什么时候一个类只需要一个对象?比如,工具类(没有属性的类,只有行为)并且该工具对象被频繁使用。权衡制用一个对象与产生多个对象的内存使用,来确定该类是否要定义为只需要一个对象
this关键字
在java基础中,this关键字是一个最重要的概念。使用this关键字可以完成一下操作:
- 调用类中的属性
- 调用类中的方法或构造方法
- 表示当前对象
值传递与引用传递
值传递
public class valueDemo{
public static void main(String[] args){
int x = 10;
method(x);
System.out.println("x="+x);
}
public static void method(int mx){
mx = 20;
}
}
引用传递
public class RefDemo{
public static void main(String[] args){
Duck d = new Duck();
method(d);
System.out.println("Duck age ="+d.age);
}
public static void method(Duck duck){
duck.age = 5;
}
}
class Duck{
int age = 2; // 省略封装
}
对象的一对一关系
两个对象之间的一对一关系:
比如:
一个英雄(Hero)对一个兵器(Weapon)
public class test10 {
public static void main(String[] args){
Hero hero = new Hero();
hero.setName("吕布");
hero.setAge(22);
Weapon weapon = new Weapon();
weapon.setName("方天画戟");
hero.setWeapon(weapon);
weapon.setHero(hero);
}
}
class Hero{
private String name;
private int age;
private Weapon weapon;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setWeapon(Weapon weapon) {
this.weapon = weapon;
}
public Weapon getWeapon() {
return weapon;
}
}
class Weapon{
private String name;
private Hero hero;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public Hero getHero() {
return hero;
}
public void setHero(Hero hero) {
this.hero = hero;
}
}
static关键字
static关键字的作用:
-
使用static关键字修饰一个属性
声明为static的关键字实质上就是一个全局变量
-
使用static关键字修饰一个方法
通常,在一个类中定义已给方法为static,那就是说,无需本类的对象即可调用此方法
-
使用static关键字修饰一个类(内部类)
- 静态变量或方法不属于对象,依赖类
- 静态变量是全局变量,生命周期从类被加载后一直到程序结束
- 静态变量只有存一份,在静态方法区中存储
- 静态变量是本类所有对象共享一份
- 建议不要使用对象名去调用静态数据,直接使用类名调用
声明为static的方法有一下的几条限制:
它们仅能调用其他的static的方法。
它们只能访问static数据。
它们不能以任何方式引用this或super。
什么时候使用static?
所有对象共同的属性或方法,那么我们应该定义为静态的。
main方法分析
//主方法:
public static void main(String[] args){
//代码块
}
public:共有的,最大的访问权限
static:静态的,无需创建对象
void:表示没有返回值,无需向JVM返回结果
main:方法名,固定的方法名
String[] args: 表示参数为字符串数组
代码块
普通代码块
直接写在方法中的代码块就是普通代码块
示例:
public class Demo1{
public static void main(String[] args){
{
//普通代码块
String info = "局部变量-1";
System.out.println(info);
}
String info = "局部变量-2";
System.out.println(info);
}
}
构造块是在类中定义的代码块
class Demo{
{
//代码块
System.out.println("构造块")
}
public Demo(){
System.out.println("构造方法")
}
}
在类中使用static声明的代码块称为静态代码块
在第一次使用的时候被调用(创建对象),只会执行一次,优于构造块执行
class Demo{
{
System.out.println("构造块")
}
static{
System.out.println("静态代码块")
}
public Demo(){
System.out.println("构造方法")
}
}
同步代码块
单例设计模式
单例设计模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
- 构造方法私有化
- 声明一个本类对象
- 给外部提供一个静态方法获取对象实例
两种实现方式:
1.饿汉式:在第一次调用getInstance方法时,对象被创建,到程序结束后释放
class Singleton1{
private Singleton1(){}
private static Singleton1 s = new Singleton1();
public static Singleton1 getInstance(){
return s;
}
public void print(){
System.out.println("测试方法")
}
}
2.懒汉式:在类被加载后,对象被创建,到程序结束
class Singleton2{
private Singleton2(){}
private static Singleton2 s;
public static Singleton2 getInstance(){
if(s==null){
s = new Singleton2();
}
return s;
}
public void print(){
System.out.println("测试方法")
}
}
对象数组与管理
对象数组就是数组里的每个元素都是类的对象,赋值时先定义对象,然后将对象直接复制给数组。
示例:
Chicken[] cs = new Chicken[10];
使用对象数组实现多个Chicken的管理。
继承的基本概念
继承是从已有的类创建新类的过程
- 继承是面向对象三大特征之一
- 被继承的类成为父类(超类),继承父类的类称为子类(派生类)
- 继承是指一个对象直接使用另一对象的属性和方法
- 通过继承可以实现代码重用
protected访问权限修饰符,在继承关系中使用,在父类中使用protected修饰的属性或方法可以被子类继承
创建子类对象时,父类的构造方法也会被调用,为什么?
因为子类要使用到父类的数据,那么就要通过父类的构造方法来初始化数据,
如果传见子类对象时使用过默认的构造方法,那么父类的默认构造方法也会被调用
如果创建子类对象时会调用父类的默认构造方法
class Dog{
protected name;
protected age;
public void eat(){
System.out.println("开始干饭了")
}
}
class HomeDog extends Dog{
public void say(){
System.out.println("我是家狗")
}
}
继承小结
- 继承是发生在多个类之间
- 继承使用关键字extends
- JAVA只能单继承,允许多层继承
- 被继承的累叫父类(超类),继承父类的类叫子类(派生类)
- 在父类中的非私有属性和方法可以被子类继承
- protected(受保护的访问权限修饰符),修饰的属性或方法可以被子类继承
- 构造方法不能被继承
- 创建对象会调用构造方法,调用构造方法不一定就是创建该类对象
- 实例化子类对象,会先调用父类的构造方法,如果父类中没有默认的构造方法,那么子类必须显示的通过super(…)来调用父类的带参数构造方法,super也只能在子类构造方法中的第一句
继承的好处:
- 提高代码的复用性
- 提高代码的维护性
- 让类与类之间产生关系,是多态的前提
继承的缺点
增强了类与类之间的耦合性
开发原则:高内聚,低耦合
方法的重写
在Java中,子类可继承父类中的方法,而不需要重新编写相同的方法。但有时子类并不想原封不动地继承父类的方法,而是想做一定的修改,这就需要采用方法的重写。方法重写又称方法覆盖。
在子类和父类中,重写方法后,在调用时,已创建的对象类型为准,会调用谁的方法。
关于方法重写的一些特性:
- 发生在子父类中,方法重写的两个方法返回值、方法名、参数列表必须完全一致(子类重写父类的方法)
- 子类抛出的异常不能超过父类相应方法抛出异常(子类异常不能大于父类异常)
- 子类方法的访问级别不能低于父类相应方法的访问类别(子类访问级别不能低于父类访问级别)
- 父类中的方法若是应用private、static、final任意修饰符修饰,那么,不能被子类重写
为什么要重写方法?或者方法重写的目的是什么?
若子类从父类中继承过来的方法,不能满足子类特有的需求时,子类就需要重写父类中相应的方法,方法的重写也是程序扩展的体现
class Dog{
protected name;
protected age;
public void eat(){
System.out.println("开始干饭了")
}
}
class HomeDog extends Dog{
public void eat(){
System.out.println("我爱吃米饭")
}
}
super关键字
super可以完成一下的操作:
- 使用super调用父类中的属性,可以从父类实例处获取信息
- 使用super调用父类中的方法,可以委托父类对象帮助完成某件事情
- 使用super调用父类中的构造方法(super(实参)形式),必须在子类构造方法的第一条语句,调用父类中相应的构造方法,若不显示的写出来,默认调用父类的无参构造方法,比如:super()
继承的应用示例
-
定义一个化妆品类(Cosmetic)
-
定义一个化妆品管理类(CosmeticManager)
实现进货功能
可以输出所有化妆品信息功能
-
使用继承实现一个可按单价排序输出所有化妆品的功能
-
使用继承实现一个只输出进口化妆品的功能
import java.util.Arrays;
public class java12 {
public static void main(String[] args) {
ImportCosmeticManager cm = new ImportCosmeticManager();
cm.add(new Cosmetic("香奈儿","进口",1000));
cm.add(new Cosmetic("圣罗兰","进口",800));
cm.add(new Cosmetic("大宝","国产",20));
cm.add(new Cosmetic("万紫千红","国产",10));
cm.printAll();
}
}
class CosmeticManager{
protected Cosmetic[] cs = new Cosmetic[4];
protected int count=0;
// 进货功能
public void add(Cosmetic c){
if(count>=cs.length){
int newLen = cs.length*2;
cs = Arrays.copyOf(cs,newLen);
}
cs[count] = c;
count++;
}
public void printAll(){
for (int i=0;i<cs.length;i++){
cs[i].getInfo();
}
}
}
// 输出进口商品
class ImportCosmeticManager extends CosmeticManager{
public void printAll(){
for(int i=0;i<count-1;i++){
if("进口".equals(cs[i].getType())){
cs[i].getInfo();
}
}
}
}
//按照价格排序
class SortCosmeticManager extends CosmeticManager{
public void printAll(){
Cosmetic[] temp = Arrays.copyOf(cs,count);
Cosmetic c = null;
for(int i = 0;i<count-1;i++){
for(int j = 0;j<count-i-1;j++){
if(temp[j].getPrice()>temp[j+1].getPrice()){
c = temp[j];
temp[j] = temp[j+1];
temp[j+1] = c;
}
}
}
for(Cosmetic n:temp){
n.getInfo();
}
}
}
// 化妆类
class Cosmetic{
private String name;// 品牌
private String type;// 进口或国产
private int price;// 价格
public Cosmetic(String name,String type,int price){
this.name = name;
this.type = type;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public void getInfo(){
System.out.println("name="+getName()+",type="+getType()+",price="+getPrice());
}
}
final关键字
使用final关键字完成以下的操作:
-
使用final关键字声明一个常量
修饰属性或者修饰局部变量(最终变量),也称为常量。
-
使用final关键字声明一个方法
该方法为最终方法,且只能被子类继承,但不能被子类重写。
-
使用final关键字声明一个类
该类就转变为最终类,没有子类的类,final修饰的类无法被继承。
-
在方法参数中使用final,在该方法内部不能修改参数的值(在内部类中详解)
抽象类
抽象类的基本概念
- 很多具有相同特征和行为的对象可以抽象为一个类,很多具有相同特征和行为的类可以抽象为一个抽象类
- 使用abstract关键字声明的类为抽象类
abstract class Animal{
public abstract void move() // 方法的声明,抽象方法只有声明,没有实现
}
// 继承抽象类的具体类必须实现所有抽象方法
abstract class Person extends Animal{
public abstract void eat()
}
class Man extends Person{
public void move(){}
public void eat(){}
}
- 抽象类可以没有抽象方法,有抽象方法的类必须是抽象类
- 非抽象类继承抽象类必须实现所有抽象方法
- 抽象类可以继承抽象类,可以不是先父类抽象方法
- 抽象类可以有方法实现和属性
- 抽象类不能被实例化
- 抽象类不能声明为final
- 抽象类可以有构造方法
接口
接口的概念
- 接口是一组行为的规范 、定义,没有实现
- 使用接口,可以让我们的程序更加利于变化
- 接口是面向对象编程体系中的思想精髓之一
- 面向对象设计法则:基于接口编程
接口的定义格式
interface 接口名称{
全局常量;
抽象方法;
}
interface IEat{
// public abstract void eat(); //接口中只能定义抽象方法
void eat(); // 接口中定义的方法没有声明修饰符,默认为public abstract
int Num = 10; // 常量
}
interface IRun{
void run()
}
// 接口之间可以多继承(注意:类是只能单继承的)
interface ISleep extends IEat,IRun{
void sleep();
}
class Girl implements ISleep,IEat{
// 具体实现
}
接口的使用规则:
- 定义一个接口,使用interface关键字
- 在一个接口中,只能定义常量、抽象方法,JDK1.8后可以定义默认的实现方法
- 接口可以继承多个接口
- 一个具体类实现接口使用implements关键字
- 一个类可以实现多个接口
- 抽象类实现接口可以不实现接口的方法
- 在接口中定义的方法没有声明访问修饰符,默认为public
- 接口不能有构造函数
面向对象设计原则:
- 对修改关闭,对扩展开放
- 面向接口编程
多态性
多态是面向对象三大特征之一
什么是多态性 ?
对象在运行过程中的多种规范
多态性我们大概可以分为两类:
- 方法的重载与重写
- 对象的多态性
例如:
// 用父类的引用指向子类对象(用大的类型去接受小的类型,向上转型、自动转换)
Chicken home = new HomeChicken();
结论:
在编程时针对抽象类型的编写代码,成为面向对象编程(或面向接口编程)父类通常都定义为抽象类、接口
对象的多态性
对象多态性是从继承关系中的多个类而来
向上转型:将子类实例转为父类实例
格式:父类 父类对象 = 子类实例:=>自动转换
以基本数据类型操作为例: int i = ‘a’;
(因为char的容量比int小,所以可以自动完成)
**向下转型:**将父类实例转为子类实例
格式:子类 子类对象 = (子类)父类实例:强制转换
以基本数据类型操作为例: char c = (char)97;
因为整型是4个字街比char2个字节更大,所以需要强制完成
多态性小结
- 方法的重载与重写就是方法的多态性表现
- 多个子类就是父类中的多种形态
- 父类引用可以指向子类对象,自动转换
- 子类兑现指向父类引用需要强制转换(注意:类型不会会报异常)
- 在实际开发中尽量使用父类引用(更利于扩展)
instanceof关键字
instanceof是用与检查是否为指定的类型,通常在把父类引用强制转换为子类引用时要使用,以避免发生类型转换异常
语法格式如下:
对象 instanceof 类型 --返回boolean类型值
示例:
if(homeChicken instanceof Chicken){}
该语句一般用于判断一个对象是否为某个类的实例,是返回true,否返回false
父类的设计法则
通过instanceof关键字,我们可以很方便的检查对象的类型,但如果一个父类的子类过多,这样的判断还是显得繁琐,那么如何去设计一个父类呢?
- 父类通常情况下都设计为抽象类或接口,其中优先考虑接口,如接口不能满足才考虑抽象类
- 一个具体的类尽可能不去继承另一个具体类,这样的好处是无需检查对象是否为父类对象
抽象类应用-模板方法模式
模板方法模式(Templete Method):定义一个操作中的算法的骨架,而将一些可变部分的实现延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定的步骤。
public class test12 {
public static void main(String[] args) {
UserManager um = new UserManager();
um.action("admin","del");
}
}
abstract class BaseManager{
public void action(String name,String method){
if("admin".equals(name)){
execute(method);
}else{
System.out.println("你没有操作权限,请联系管理员");
}
}
public abstract void execute(String execute);
}
class UserManager extends BaseManager{
public void execute(String method){
if("add".equals(method)){
System.out.println("执行添加方法");
}else if("del".equals(method)){
System.out.println("执行删除方法");
}
}
}
接口的应用-策略模式
策略模式(Strategy Pattern),定义了一系列的算法,将每一种算法封装起来并可以相互替换使用,策略模式让算法独立于使用它的客户应用而独立变换
OO设计原则:
- 面向接口编程(面向抽象编程)
- 封装变化
- 多用组合,少用继承
import sun.awt.geom.AreaOp;
//策略模式
public class test1 {
public static void main(String[] args){
BaseSerive user = new UserSerive();
user.setIsSave(new NetSave());
user.add("用户");
}
}
interface IsSave{
public void save(String data);
}
class FileSave implements IsSave{
@Override
public void save(String data) {
System.out.println("把数据保存到文件中"+data);
}
}
class NetSave implements IsSave{
@Override
public void save(String data) {
System.out.println("把数据保存到网络中"+data);
}
}
abstract class BaseSerive{
private IsSave isSave; // 私有属性
public void setIsSave(IsSave isSave) {
this.isSave = isSave;
}
public void add(String data) {
System.out.println("检验数据合法性");
isSave.save(data);
System.out.println("数据保存成功");
}
}
class UserSerive extends BaseSerive{
}
Objec类
object类是类层次结构的根类
每个类都使用Object作为超类。所有对象(包括数组)都实现这个类的方法
所有类都是Object类的子类。
toString方法
返回该对象的字符串表示
通常,toString方法会返回一个“以文本方式表示”此对象的字符串。结果应是一个简明且易于读懂的信息表达式。建议所有子类都重写此方法。
class Student{
private String name;
private int sid;
private int age;
public Student(){}
public Student(int sid,String name,int age){
this.name = name;
this.sid = sid;
this.age age;
}
// 重写Object类中的toString方法
public String toString(){
return "sid="+sid+",name="+name+",age="+age;
}
}
equals方法
指示其他某个对象是否与此对象“相等”,equals方法在非空对象引用上实现相等关系:
自反性 对称性 传递性 一致性
finalize方法
当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。子类重写finalize方法,以配置系统资源或执行其他清除
getClass方法
返回次Object的运行时类
简单工厂模式
简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单使用的模式。
// 使用工厂来降低两者之间的依赖
// 使用者和被使用者两者之间,耦合,产生了依赖,当被使用者改变时,会影响使用者
class ProductFactory{
public static Product getProduct(String name){
if("phone".equals(name)){
return new Phone();
}else if("computer".equals(name)){
return new Computer;
}else{
return null
}
}
}
interface Product{
public void work();
}
class Phone implements Product{
public void work(){
System.out.println("手机开始工作。。。")
}
}
class Computer implements Product{
public void work(){
System.out.println("电脑开始工作。。。")
}
}
静态代理模式
代理模式(Proxy):为其他对象提供一种代理以控制对这个对象的访问
代理模式说白了就是”真实对象“的代表,在访问对象时引用一定程度的间接性,因为这种间接性可以附加多种用途。
public class test13 {
public static void main(String[] args) {
UserAction userAction = new UserAction();
ActionProxy proxy = new ActionProxy(userAction);
proxy.doAction();
}
}
interface Action{
public void doAction();
}
class ActionProxy implements Action{
private Action target;
public ActionProxy(Action target){
this.target = target;
}
@Override
public void doAction() {
long startTime = System.currentTimeMillis();
target.doAction();
long endTime = System.currentTimeMillis();
System.out.println("共耗时"+(endTime-startTime));
}
}
class UserAction implements Action{
@Override
public void doAction() {
for(int i = 0;i < 100;i++){
System.out.println("用户开始工作...");
}
}
}
适配器模式
适配器模式(Adapter):将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
oo设计原则:
- 面向接口编程(面向抽象编程)
- 封装变化
- 多用组合,少用继承
- 对修改关闭,对扩展开放
public class test14 {
public static void main(String[] args) {
PowerA powerA = new PowerAImp1();
PowerB powerB = new PowerBImp1();
Adapter adapter = new Adapter(powerB);
work(adapter);
}
public static void work(PowerA a){
System.out.println("正在连接。。。");
a.insert();
System.out.println("工作结束。。。");
}
}
class Adapter implements PowerA{
private PowerB powerB;
public Adapter(PowerB powerB){
this.powerB = powerB;
}
@Override
public void insert() {
powerB.connect();
}
}
interface PowerA{
public void insert();
}
class PowerAImp1 implements PowerA{
@Override
public void insert() {
System.out.println("电源A开始工作");
}
}
interface PowerB{
public void connect();
}
class PowerBImp1 implements PowerB{
@Override
public void connect() {
System.out.println("电源B开始工作");
}
}
内部类
内部类就是在一个类的内部定义的类
成员内部类
格式如下:
class Outer{
class Inner{}
}
编译上诉代码会产生两个文件:
Outer.class和Outer$Inner.class
方法内部类
内部类可以作为一个类的成员外,还可以把类放在方法内定义
注意:
- 方法内部类只能在定义该内部类的方法 内实例化,不可以在此方法外对其实例化
- 方法内部类对象不能使用该内部类所有方法的非final局部变量
静态内部类
在一个类内部定义一个静态内部类
静态的含义是该内部类可以像其他静态成员一样,没有外部类对象时,也能够访问它。静态嵌套类仅能访问外部类的静态成员和方法
匿名内部类
匿名内部类就是没有名字的内部类。
匿名内部类的三种情况:
- 继承式的匿名内部类
- 接口式的匿名内部类
- 参数式的匿名内部类
在使用匿名内部类时,要记住一下几个原则:
- 不能有构造方法,只能有一个实例
- 不能定义任何静态成员、静态方法
- 不能是public,protected,private,static
- 一定是在new的后面,用其隐含实现一个接口或实现一个类
- 匿名内部类为局部的,所以局部内部类的所有限制都对其生效
public class test15 {
public static void main(String[] args) {
Outer outer = new Outer();
outer.innerPrint();
outer.show();
Outer.Inner3 inner3 = new Outer.Inner3();
inner3.print();
outer.print1();
outer.print2();
outer.print3(new Eat() {
@Override
public void eat() {
System.out.println("eat:参数式匿名内部类");
}
});
// Outer.Inner inner = outer.new Inner();
// inner.print();
}
}
class Outer{
private String name;
public void innerPrint(){
Inner inner = new Inner();
inner.print();
}
// 成员内部类
private class Inner{
public void print(){
System.out.println("成员内部类");
}
}
// 方法内部类
public void show(){
class Inner2{
public void print(){
System.out.println("方法内部类");
}
}
Inner2 inner2 = new Inner2();
inner2.print();
}
// 静态内部类
static class Inner3{
public void print(){
System.out.println("静态内部类");
}
}
// 匿名内部类
public void print1(){
Cat cat = new Cat(){
@Override
public void eat() {
System.out.println("eat:继承式匿名内部类");
}
};
cat.eat();
}
public void print2(){
Eat eat = () -> System.out.println("eat:接口式匿名内部类");
eat.eat();
}
public void print3(Eat eat){
eat.eat();
}
}
abstract class Cat{
public abstract void eat();
}
interface Eat{
void eat();
}
问题:局部内部类访问局部变量必须用final修饰,为什么?
当调用这个方法时,局部变量如果没有final修饰,它的生命周期和方法的生命周期是一样的,当方法被调用时会入栈,方法结束后即弹栈,这个局部变量也会消失,那么如果局部内部类对象还没有马上消失想用这个局部变量,显然已经没法使用了,如果用final修饰会在类加载的时候进入常量池,即使方法弹栈,常量池的常量还在,也就可以继续使用了
c,protected,private,static
4. 一定是在new的后面,用其隐含实现一个接口或实现一个类
5. 匿名内部类为局部的,所以局部内部类的所有限制都对其生效
public class test15 {
public static void main(String[] args) {
Outer outer = new Outer();
outer.innerPrint();
outer.show();
Outer.Inner3 inner3 = new Outer.Inner3();
inner3.print();
outer.print1();
outer.print2();
outer.print3(new Eat() {
@Override
public void eat() {
System.out.println("eat:参数式匿名内部类");
}
});
// Outer.Inner inner = outer.new Inner();
// inner.print();
}
}
class Outer{
private String name;
public void innerPrint(){
Inner inner = new Inner();
inner.print();
}
// 成员内部类
private class Inner{
public void print(){
System.out.println("成员内部类");
}
}
// 方法内部类
public void show(){
class Inner2{
public void print(){
System.out.println("方法内部类");
}
}
Inner2 inner2 = new Inner2();
inner2.print();
}
// 静态内部类
static class Inner3{
public void print(){
System.out.println("静态内部类");
}
}
// 匿名内部类
public void print1(){
Cat cat = new Cat(){
@Override
public void eat() {
System.out.println("eat:继承式匿名内部类");
}
};
cat.eat();
}
public void print2(){
Eat eat = () -> System.out.println("eat:接口式匿名内部类");
eat.eat();
}
public void print3(Eat eat){
eat.eat();
}
}
abstract class Cat{
public abstract void eat();
}
interface Eat{
void eat();
}
问题:局部内部类访问局部变量必须用final修饰,为什么?
当调用这个方法时,局部变量如果没有final修饰,它的生命周期和方法的生命周期是一样的,当方法被调用时会入栈,方法结束后即弹栈,这个局部变量也会消失,那么如果局部内部类对象还没有马上消失想用这个局部变量,显然已经没法使用了,如果用final修饰会在类加载的时候进入常量池,即使方法弹栈,常量池的常量还在,也就可以继续使用了