这个是我在观看尚硅谷的视频时的笔记
目录
实例2:使用 switch 把小写类型的 char 型转为大写。只 转换 a, b, c , d, e. 其它的输出 “other 。
实例:定义一个int 型的一维数组,包含 10 个元素,分别赋一些随机整数,然后求出所有元素的最大值, 最小值,求和值,平均值,并输出出来 。
类 (Class) 和 对象 (Object) 是面向对象的核心概念。
实例:定义一个 int 型的数组: {12,3,3,34,56,77,432};让数组的每个位置上的值去除以首位置的元素,得到的结果,作为该位置上的新值。遍历新的数组。
实例:方法声明的形参类型为 父类 类型,可以使用 子类的对象 作为实参调用 该 方法
实例:建立InstanceTest 类,在类中定义方法method(Person e);
实例:定义三个类,父类 GeometricObject 代表几何形状,子类 Circle 代表圆形, MyRectangle 代表矩形 。
实例:编写代码并且覆盖equals方法比较两个MyDate对象的年月日
实例:定义两个类,父类 GeometricObject 代表几何形状,子类 Circle 代表圆形。
实例:利用Vector 代替数组处理:从键盘读入学生成绩(以负数代表输入结束),找出最高分,并输出学生成绩等级。
实例:在航运公司系统中,Vehicle 类需要定义两个方法分别计算运输工具的燃料效率和行驶距离
多态的应用:模板方法设计模式(TemplateMethod)
实例:编写工资系统,实现不同类型员工(多态)的按月发放工资。如果当月出现某个Employee对象的生日,则将该雇员的工资增加100元。(取自工资系统)
ArrayIndexOutOfBoundsException
运行结果异常处理机制1:try-catch-finally(捕获)
实例: 创建两个分线程 ,让其中 一 个线程输出 1-100 之间的偶数,另 一个线程输出 1-100 之间的奇数。
面向对象学习思维导图
基本语法
变量与运算符
关键字
定义
被 Java 语言赋予了特殊含义,用做专门用途的字符串(单词)
特点
关键字中所有字母都为 小写
用于定义数据类型的关键字 | ||||
class | interface | enum | byte | short |
int | long | float | double | char |
boolean | void | |||
用于定义流程控制的关键字 | ||||
if | else | switch | case | dafault |
while | do | for | break | continue |
return | ||||
用于定义访问权限修饰符的关键字 | ||||
private | protected | public | ||
用于定义类,函数,变量修饰符的关键字 | ||||
abstract | final | static | synchronized | |
用于定义类和类之间关系的关键字 | ||||
extends | implements | |||
用于定义建立实例及引用实例,判断实例的关键字 | ||||
new | this | super | instanceof | |
用于异常处理的关键字 | ||||
try | catch | finally | throw | throws |
用于包的关键字 | ||||
package | import | |||
其他修饰符关键字 | ||||
native | strictfp | transient | volatile | assert |
*用于定义数字类型值的字面值 | ||||
true | false | null |
保留字
定义
现有 Java 版本尚未使用 但以后版本可能会作为关键字使用。
自己命名标识符时要避免使用这些保留字(goto、const)
标识符
定义
Java 对各种 变量 、 方法 和 类 等要素命名时使用的字符序列称为标识符
说明
凡是自己可以起名字的地方都叫标识符
合法标识符规则
1、由 26 个英文字母大小写, 0 9 或 $ 组成
2、数字不可以开头。
3、不可以使用关键字和保留字,但能包含关键字和保留字。
4、Java 中严格区分大小写,长度无限制。
5、标识符不能包含空格。
JAVA命名规范
1、包名 :
多单词组成时所有字母都小写 xxxyyyzzz
2、类名、接口名 :
多单词组成时,所有单词的首字母大写 XxxYyyZzz
3、变量名、方法名 :
多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首字母大写: xxxYyyZzz
4、常量名 :
所有字母都大写。多单词时每个单词用下划线连接 XXX_YYY_ZZZ
变量
概念
内存中的一个存储区域,该区域的数据可以在同一类型范围内不断变化
变量是程序中最基本的存储单元。包含变量类型、变量名和存储的值
作用
用于在内存中保存数据
使用变量注意
1、Java 中每个变量必须先 声明,后使用
2、使用变量名来访问这块区域的 数据
3、变量的作用域:其定义所在的一对 { }内4、变量只有在其作用域内才有效
5、同一个作用域内,不能定义重名的变量
声明变量
语法:<数据类型 > 变量名称
例如: int a;
变量的赋值
语法: 变量名称 > = <值>
例如: a= 10;
声明和赋值变量
语法:< 数据类型>< 变量名 >= <初始化值>
例如: int a = 10;
分类
转义字符
\b | 退格符 |
\n | 换行符 |
\r | 回车符 |
\t | 制表符 |
\" | 双引号 |
\' | 单引号 |
\\ | 反斜杠 |
整数类型
默认为int,声明long型常量需要在后面加入'l'或'L'
public class test {
public static void main(String[] args) {
int number1;
number1 = 10;
int number2;
number2 = 20;
int number3;
number3 = number1 + number2;
System.out.println("Number3 = " + number3);
int number4 = 50;
int number5 = number4 - number3;
System.out.println("Number5 = " + number5);
}
}
浮点类型
float:尾数精确到7位有效数字
double:尾数精确到14位有效数字
默认为double,申明float需要在后面加上'f'或者'F'
public class test1 {
public static void main(String[] args) {
float number1;
number1 = 1.202021516f;
double number2;
number2 = 1.202021516;
System.out.println("Number2=" + number2);
System.out.println("Number1=" + number1);
}
}
字符类型
char 型数据用来表示通常意义上字符
Java 中的所有字符都 使用 Unicode 编码,故一个字符可以存储一个字母,一个汉字,或其他书面语的一个字符。
字符型变量 的三种表现形式:
1、字符常量是用单引号 ‘’ 括起来的单个字符。
2、Java 中还允许使用转义字符‘ ‘\\’来 将其后的字符转变为特殊字符型常量。
3、直接使用 Unicode 值来表示字符型常量:‘ u\XXXX 。其中, XXXX 代表一个十六进制整数。
char 类型是可以进行运算的。因为它都对应有 Unicode 码。
public class test3 {
public static void main(String[] args) {
char A,B,C,D,E,F;
A='s';
B='=';
C='岚';
D='1';
E='\n';
F='\u1561';
System.out.println("A="+A);
System.out.println("B="+B);
System.out.println("C="+C);
System.out.println("D="+D);
System.out.println("A+B="+A+B);
System.out.println("E="+E);
System.out.println("F="+F);
}
}
布尔类型
用来判断逻辑条件,且只允许取值ture和false,不存在null
ture和false不能用非0和0的整数来表示
由于java虚拟机中没有专属于布尔类型的指令,所以Java 语言表达所操作的boolean 值
在编译之后都使用 java 虚拟机中的 int 数据类型来代替: true 用 1 表示, false用 0 表示。
字符串类型(String属于引用数据类型)
使用方式与基本数据类型一致
需要使用双引号
一个字符串可以串接另一个字符串,也可以直接串接其他类型的数据
public class test4 {
public static void main(String[] args){
String a="abc";
int b=1;
String c="def";
System.out.println("a="+a);
System.out.println("a+b="+(a+b+c));
}
}
强制类型转换
定义
自动类型转换的逆过程 将容量大的数据类型转换为容量小的数据类型 。使用时要加上强制转换符“()”但可能造成 精度降低或溢出,格外要注意 。
通常,字符串不能直接转换为基本类型 但通过基本类型对应的包装类则可以实现把字符串转换成基本类型 。
public class test2 {
public static void main(String[] args){
String a="123";
int i=Integer.parseInt(a);
System.out.println(i);
short b=5;
b=(short)(b-2);
System.out.println(b);
}
}
进制
Java 整数常量默认是 int 类型,当用二进制定义整数时,其第 32 位是符号位;
当是 long 类型时,二进制默认占 64 位,第 64 位是符号位
二进制的整数有如下三种形式:
1、原码 :直接将一个数值换成二进制数。最高位是符号位
2、负数的反码 :是对原码按位取反,只是最高位(符号位)确定为 1 。
3、负数的补码 :其反码加 1 。
计算机以二进制 补码 的形式保存所有的整数。
正数的原码、反码、补码都相同
负数的补码是其反码 +1
十进制数 | 原码 | 反码 | 补码 |
7 | 00000111 | 00000111 | 00000111 |
-7 | 10000111 | 11111000 | 11111001 |
运算符
算术运算符
运算符 | 运算 | 实例 | 结果 |
+ | 正号 | +1 | 1 |
- | 负号 | a=1,-a | -1 |
+ | 加 | 1+1 | 2 |
- | 减 | 1-1 | 0 |
* | 乘 | 1*1 | 1 |
/ | 除 | 1/1 | 1 |
% | 取余(取模) | 3/2 | 1 |
++ | 自增(前):先运算后取值 | a=1;b=++a | a=2,b=2 |
自增(后):先去之后运算 | a=1;b=a++ | a=2,b=1 | |
-- | 自减(前):先运算后取值 | a=1;b=--a | a=0,b=0 |
自减(后):先去之后运算 | a=1;b=a-- | a=0,b=1 | |
+ | 字符串连接 | "lo"+"ve" | love |
相关注意事项
1、如果对负数取模,可以把模数负号忽略不记 。 但被模数是负数则不可忽略。此外,取模运算的结果不一定总是整数。
2、对于除号“ /”,它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍弃小数部分。
3、“+”除字符串相加功能外,还能把非字符串转换成字符串
public class test5 {
public static void main(String[] args){
int a,b,c,d;
a=-2;
b=3;
c=a%b;
d=b%a;
String e,f;
e="lo";
f="ve";
System.out.println("c="+c);
System.out.println("d="+d);
System.out.println("5+5="+5+5);
System.out.println("lo+ve="+e+f);
}
}
赋值运算符
运算符 | 运算 | 实例 | 结果 |
= | 将右值赋给左侧未知数 | a=2 | a=2 |
+= | 以本身为前面的数进行加法运算 | a=1;a+=2 | a=3 |
-= | 以本身为前面的数进行减法运算 | a=1;a-=2 | a=-1 |
*= | 以本身为前面的数进行乘法运算 | a=1;a*=2 | a=2 |
/= | 以本身为前面的数进行除法运算 | a=5;a/=2 | a=2 |
%= | 以本身为前面的数进行取余运算 | a=5;a%=2 | a=1 |
1、当 “=”两侧数据类型不一致时 可以使用 自动类型转换或使用强制类型转换原则进行处理 。
2、支持连续赋值
3、使用除“=”外的赋值运算符时数据本身的类型不变
public class test6 {
public static void main(String [] args){
int i,j,k,l,m;
i=j=k=l=m=5;
i+=1;
j-=1;
k*=2;
l/=2;
m%=2;
System.out.println("5+1="+i);
System.out.println("5-1="+j);
System.out.println("5*2="+k);
System.out.println("5/2="+l);
System.out.println("5%2="+m);
}
}
比较运算符(关系运算符)
运算符 | 运算 | 实例 | 结果 |
== | 相等 | 4==3 | false |
!= | 不等于 | 4!=3 | true |
< | 小于 | 4<3 | false |
> | 大于 | 4>3 | true |
<= | 小于等于 | 4<=3 | false |
>= | 大于等于 | 4>=3 | true |
instanceof | 检查是否为类的对象 | "love"instanceof String | true |
public class test7 {
public static void main(String[] args){
class Person {
}//创建Person类
class Man extends Person {
}//创建Person的子类Man
Person p1 = new Person();
Person p2 = new Man();
Man m1 = new Man();
System.out.println(p1 instanceof Man);
System.out.println(p2 instanceof Man);
System.out.println(m1 instanceof Man);
}
}
逻辑运算符
&逻辑与,|逻辑或,!逻辑非,&&短路与,||短路或,^逻辑异或
(下表中T代表true,F代表false)
a | b | a&b | a&&b | a|b | a||b | !a | a^b |
T | T | T | T | T | T | F | F |
T | F | F | F | T | T | F | T |
F | T | F | F | T | T | T | T |
F | F | F | F | F | F | T | F |
1、逻辑运算符用于连接布尔型表达式,在 Java 中不可以写成 3<x<6 ,应该写成 x>3 & x<6 。
2、&和&&的区别:&,左边无论真假,右边都进行运算;
&&,如果左边为真,右边参与运算,如果左边为假,那么右边不参与运算 。
3、|和|| 的区别|,左边无论真假,右边都进行运算;
||,当左边为真,右边不参与运算 。
4、^ 与或 | 的不同之处是:当左右都为 true 时,结果为 false 。(求异)
位运算符(针对整数的二进制进行的运算)
运算符 | 运算 | 实例 | 本质 | 结果 |
<< | 左移 | 3<<2 | 3*2*2=12 | 12 |
>> | 右移 | 3>>1 | 3/2=1 | 1 |
>>> | 无符号右移 | 3>>>1 | 3/2=1 | 1 |
& | 与运算 | 6&3 | 110&011=010 | 2 |
| | 或运算 | 6|3 | 110|001=111 | 7 |
^ | 异或运算 | 6^3 | 110^001=101 | 5 |
~ | 取反运算 | ~3 ~-3 | ~0011=1100+1=1101 ~1011=1100+1=1101 取反=0010 | -4 2 |
<< | 空位补0 ,被移除的高位丢弃,空缺位补 0 | |||
>> | 被移位的二进制最高位是0 ,右移后,空缺位补 0; 最高位是1 ,空缺位补 1 | |||
>>> | 被移位二进制最高位无论是0 或者是 1 ,空缺位都用 0 补。 | |||
& | 二进制位进行运算,只有 1&1 时结果是 1 ,否则是 0; | |||
| | 二进制位进行| 运算,只有 0 | 0 时结果是 0 ,否则是 1; | |||
^ | 相同二进制位进行^ 运算,结果是 0 1^1=0 , 0^0=0; 不相同二进制位^ 运算结果是 1 。 1^0=1 , 0^1=1 | |||
~ | 正数取反,各二进制码按补码各位取反; 负数取反,各二进制码按补码各位取反 |
三元运算符
(条件表达式)?表达式1:表达式2
public class test8 {
public static void main(String [] ags){
int a,b;
a=10;
b=-10;
a=a>0?a:-a;
b=b>0?b:-b;
System.out.println("a="+a);
System.out.println("b="+b);
}
}
程序流程控制
顺序结构
Java中定义成员变量时采用合法的前向引用
分支语句
if-else结构
1.if(条件){
执行代码块;
}
2、if(条件表达式){
执行代码块1;
}
else{
执行代码块2;
}
3.
if(条件表达式){
执行代码块1;
}
else if{
执行代码块2;
}
……
else{
执行代码块n;
}
实例:利用if-else语句输出三个数中最大值
import java.util.Scanner;
public class test1 {
public static void main(String[] args) {
Scanner number = new Scanner(System.in);
int a, b, c,max;
System.out.println("请输入a:>");
a = number.nextInt();
System.out.println("请输入b:>");
b = number.nextInt();
System.out.println("请输入c:>");
c = number.nextInt();
max=a;
if(a<b){
max=b;
if(b<c) {
max = c;
}
}
else if(a<c){
max=c;
if(c<b){
max=b;
}
}
System.out.println("max="+max);
}
}
switch-case结构
switch(表达式){
case 常量1:
语句1;
break;
……
case 常量n:
语句n;
berak;
default:
语句
break;
}
实例1:输入季节英文单词出现中文单词
import java.util.Scanner;
public class test2 {
public static void main(String[] args) {
System.out.println("please input season word:>");
Scanner Season = new Scanner(System.in);
String season = Season.nextLine();
switch (season) {
case "spring":
System.out.println("春天");
break;
case "summer":
System.out.println("夏天");
break;
case "autumn":
System.out.println("秋天");
break;
case "winter":
System.out.println("冬天");
break;
default:
System.out.println("季节输入错误");
break;
}
}
}
1、switch( 表达式 中 表达式的值 必须 是下述几种类型之一:byte;short;char ;int; 枚举(jdk5.0);String (jdk 7.0)
2、case 子句中的值必须是 常量 ,不能是变量名或不确定的表达式值
3、同一 个 switch 语句,所有 case 子句中的常量值互不相同
4、break 语句用来在执行完一个 case 分支后使程序跳出 switch 语句块;如果没有 break ,程序会顺序执行到 switch 结尾
5、default 子句是可任选的 。同时,位置也是灵活的。当 没有匹配的 case 时,执行 default
实例2:使用 switch 把小写类型的 char 型转为大写。只 转换 a, b, c , d, e. 其它的输
出 “other 。
import java.util.Scanner;
public class test3 {
public static void main(String[] args) {
System.out.println("please input a word:>");
Scanner Word = new Scanner(System.in);
char word = Word.next().charAt(0);
switch (word) {
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
char temp=(char)(word - 32);
System.out.println(temp);
break;
default:
System.out.println("other");
break;
}
}
}
循环结构
for循环
for(① 初始化部分; ② 循环条件部分;④ 迭代部分){
③循环体部分;
}
说明
1、②循环条件部分为 boolean 类型表达式,当值为 false 时,退出循环
2、①初始化部分可以声明多个变量,但必须是同一个类型,用逗号分隔
3、④可以有多个变量更新,用逗号分隔
实例:水仙花数
public class test4 {
public static void main(String[] args) {
int i, l, m, n;
for (i = 100; i < 999; i++) {
l = i / 100;
m = i / 10 - 10 * l;
n = i - 100 * l - 10 * m;
if (i == m * m * m + n * n * n + l * l * l) {
System.out.println(i);
}
}
}
}
while循环
①初始化部分;
while(② 循环条件部分){
③循环体部分;
④迭代部分;
}
1、不要忘记声明 ④ 迭代部分 。 否则 循环将不能结束 变成死循环 。
2、for 循环和 while 循环可以相互转换
实例:水仙花数
public class test5 {
public static void main(String[] args) {
int i, l, m, n;
i = 100;
while (i < 999) {
i++;
l = i / 100;
m = i / 10 - 10 * l;
n = i - 100 * l - 10 * m;
if (i == m * m * m + n * n * n + l * l * l) {
System.out.println(i);
}
}
}
}
do-while循环
①初始化部分;
do{
③循环体部分;
④迭代部分;
}
while( ② 循环条件部分)
do-while 循环至少执行一次循环体
嵌套循环
1、将一个循环放在另一个循环体内,就形成了嵌套循环。其中,for ,while ,do…while 均可以作为 外层循环或内层循环 。
2、实质上,嵌套循环就是把内层循环当成外层循环的循环体。当只有内层循环的循环条件为 false 时,才会完全跳出内层循环,才可结束外层的当次循环,开始下一次的循环。
3、设外层循环次数为 m 次,内层为 n 次,则内层循环体实际上需要 执行 m*n 次 。
实例1:九九乘法表
public class test6 {
public static void main(String []args){
int i,j;
for(i=1;i<10;i++){
for(j=i;j<10;j++){
System.out.print(i+"*"+j+"="+(i*j)+"\t");
}
System.out.println("\n");
}
}
}
实例2:100以内所有质数
public class test7 {
public static void main(String[] args) {
int i, j, k;
for (i = 2; i < 100; i++) {
Boolean flag = true;
for (j = 2; j <= i / 2; j++) {
if (i % j == 0) {
flag = false;
break;
}
}
if (flag) {
System.out.print(i + "\t");
}
}
}
}
特殊流程控制语句
1、break 语句
(1)break 语句用于终止某个语句块 的执行
(2)break 语句出现在多层嵌套的语句块中时,可以通过标签指明要终止的是哪一层语句块
2、continue 语句
(1)continue 只能使用在循环结构中
(2)continue 语句用于 跳过其所在循环 语句块的 一 次 执行,继续下一次循环
(3)continue 语句出现在多层嵌套的循环语句体中时,可以通过标签指明要跳过的是哪一层循环
3、return语句
(1)并非专门用于结束循环的,它的功能是结束一个方法。当一个方法执行到一个 return 语句时,这个方法将被结束。
(2)与 break 和 continue 不同的是, return 直接结束整个方法,不管这个 return 处于多少层循环之内
数组
概述
定义
1、数组本身是引用数据类型而数组中的元素可以是任何数据类型包括基本数据类型和引用数据类型 。
2、创建数组对象会在内存中开辟一整块连续的空间而数组名中引用的是这块连续空间的首地址 。
3、数组的长度一旦确定就不能修改 。
4、我们可以直接通过下标 或索引 的方式调用指定位置的元素 速度很快 。
分类
1、按照维度:一维数组 、 二维数组 、 三维数组 、……
2、按照元素的数据类型分:基本数据类型元素的数组 、 引用数据类型元素的数组(即对象数组)
一维数组使用
一维数组的声明方式:
int a[]
int[] a
注意
Java 语言中声明数组时不能指定其长度 数组中元素的数
int a[5];//错误声明
数组初始化
动态初始化:数组声明且为数组元素分配空间 与 赋值的操作分开进行。
int[] number=new int[3];
number[0]=5;
number[1]=2;
number[2]=0;
String names[];
names=new String[3];
names[0]="张三";
names[1]="李四";
names[2]="王五";
静态初始化:在定义数组的同时就为数组元素分配空间并赋值。
int[] number=new int[]{5,2,0};
int[] number={5,2,0};
String names[]={"张三","李四","王五"};
1、定义并用运算符new为之分配空间后,才可以引用数组中的每个元素;
2、数组元素的引用方式:数组名[数组元素下标]
(1)数组元素下标可以是整型常量或整型表达式。如a[3] , b[i] , c[6*i];
(2) 数组元素下标从0开始;长度为n的数组合法下标取值范围: 0 —>n-1;如int a[]=new int[3]; 可引用的数组元素为a[0]、a[1]、a[2]
3、每个数组都有一个属性length指明它的长度,例如:a.length 指明数组a的长度(元素个数)数组一旦初始化,其长度是不可变的
4、 数组是引用类型,它的元素相当于类的成员变量,因此数组一经分配空间,其中的每个元素也被按照成员变量同样的方式被隐式初始化。
(1)对于基本数据类型而言,默认初始化值各有不同
(2)对于引用数据类型而言,默认初始化值为null(注意与0不同!)
数组元素类型 元素默认初始值 byte 0 short 0 int 0 long 0L float 0.0F double 0.0 char 0或者'\u0000'(表现为空) boolean false 引用类型 null
内存简化结构
一维数组的内存解析
int[] arr=newint{1,2,3};
String[] arr1=new String[4];
arr[1]="张三";
arr[2]="李四";
实例:从键盘读入4位学生成绩,找出最高分并输出
import java.util.Scanner;
public class test8 {
public static void main(String []args){
System.out.println("please input four scores:>");
Scanner Score=new Scanner(System.in);
int []score=new int[4];
int i,j,max,l,temp;
for(i=0;i<4;i++) {
score[i] = Score.nextInt();
}
max=score[0];
for(i=1;i<4;i++){
if(score[i]>max){
max=score[i];
}
}
System.out.println("最高分为"+max);
for(i=0;i<4;i++){
if(score[i]>=max-10){
System.out.println("第"+(i+1)+"位同学评分为A");
continue;
}
if(score[i]>=max-20){
System.out.println("第"+(i+1)+"位同学评分为B");
continue;
}
if(score[i]>=max-30){
System.out.println("第"+(i+1)+"位同学评分为C");
continue;
}
else
System.out.println("第"+(i+1)+"位同学评分为D");
}
}
}
多维数组使用
1、Java 语言里提供了支持多维数组的语法。
2、如果说可以把一维数组当成几何中的线性图形,那么二维数组就相当于是一个表格,像Excel中的表格一样。
3、对于二维数组的理解,我们可以看成是一维数组array1又作为另一个一维数组array2的元素而存在。其实,从数组底层的运行机制来看,其实没有多维数组。
二维数组初始化
1、动态初始化
(1)int[][] arr = new int[3][2];
- 定义了名称为arr的二维数组
- 二维数组中有3个一维数组
- 每一个一维数组中有2个元素
(2)int[][] arr = new int[3][];
- 二维数组中有3个一维数组。
- 每个一维数组都是默认初始化值null (注意:区别于格式1)
- 可以对这个三个一维数组分别进行初始化
2、静态初始化
int[][] arr = new int[][]{{3,8,2},{2,7},{9,0,1,6}};
- 定义一个名称为arr的二维数组,二维数组中有三个一维数组每一个一维数组中具体元素也都已初始化
- 第一个一维数组 arr[0] = {3,8,2};
- 第二个一维数组 arr[1] = {2,7};
- 第三个一维数组 arr[2] = {9,0,1,6};
注意特殊写法情况:int[] x,y[]; x是一维数组,y是二维数组。
Java中多维数组不必都是规则矩阵形式
int[][] arr1=new int[4][];
arr1[1]=new int[]{1,2,3};
arr1[2]=new int[4];
arr[2][1]=30;
实例:杨辉三角(10行)
public class test1 {
public static void main(String[] args) {
int[][] arr=new int[10][10];
for(int i=0;i<10; i++) {
for(int j=0;j<=i;j++) {
if(j==0||j==i) {
arr[i][j]=1;
}else {
arr[i][j]=arr[i-1][j-1]+arr[i-1][j];
}
System.out.print(arr[i][j]+"\t");
}
System.out.println();
}
}
}
数组中常见的算法
1.数组元素的赋值(杨辉三角、回形数)等
2.求数值型数组中元素的最大值、最小值、平均数、总和等
3.数组 的复制 、反转、查找 (线性查找、二分法查找)
4.数组元素的排序算法
实例:定义一个int 型的一维数组,包含 10 个元素,分别赋一些随机整数,然后求出所有元素的最大值, 最小值,求和值,平均值,并输出出来 。
import java.util.Scanner;
public class test2 {
public static void main(String[] args) {
int[] num = new int[10];
System.out.println("please input 10 numbers:>");
Scanner Num = new Scanner(System.in);
int i, j, temp, max, min, sum;
double aver;
i = sum = 0;
for (; i < 10; i++) {
num[i] = Num.nextInt();
sum += num[i];
}
aver = (double) (sum / 10);
for (i = 0; i < 10; i++) {
for (j = i; j < 10; j++) {
if (num[i] < num[j]) {
temp = num[i];
num[i] = num[j];
num[j] = temp;
}
}
}
max = num[0];
min = num[9];
System.out.println("max=" + max);
System.out.println("min=" + min);
System.out.println("sum=" + sum);
System.out.println("aver=" + aver);
}
}
排序算法
1、概念
- 假设含有 n 个记录的序列为 {R1 R2 Rn 其相应的关键字序列为{K1 K2 Kn 。将这些记录重新排为 {Ri1, Rin 使得相应的关键字值满足条 Ki1<=Ki2<=...<=Kin, 这样的一种操作称为排序。
2、衡量排序算法的优劣:
- 时间复杂度 :分析关键字的比较次数和记录的移动次数
- 空间复杂度: 分析排序算法中需要多少辅助内存
- 稳定性: 若两个记录 A 和 B 的关键字值相等,但排序后 A 、 B 的先后次序保持不变,则称这种排序算法是稳定的。
3、分类
- 内部排序 :整个排序过程不需要借助于外部存储器(如磁盘等),所有排序操作都在内存中完成。
- 外部排序 :参与排序的数据非常多,数据量非常大,计算机无法把整个排序过程放在内存中完成,必须借助于外部存储器(如磁盘)。外部排序最常见的是多路归并排序。可以认为外部排序是由多次内部排序组成。
4、常见内部排序方法
- 直接选择排序
- 堆排序
- 冒泡排序
- 快速排序
- 直接插入排序
- 折半插入排序
- 希尔排序
- 归并排序
- 桶式排序
- 基数排序
5、内部排序性能比较
- 从平均时间而言:快速排序最佳。 但在最坏情况下时间性能不如堆排序和归并排序。
- 从算法简单性看 :由于直接选择排序、直接插入排序和冒泡排序的算法比较简单,将其认为是简单算法。 对于 Shell 排序、堆排序、快速排序和归并排序算法,其算法比较复杂,认为是复杂排序。
- 从稳定性看 :直接插入排序、冒泡排序和归并排序时稳定的;而直接选择排序、快速排序、 Shell 排序和堆排序是不稳定排序。
- 从待排序的记录数 n 的大小:看 n 较小时,宜采用简单排序;而 n 较大时宜采用复杂排序
6、排序算法的选择
- 若n较小,如 (n≤50) ,可采用直接插入或直接选择排序。
- 若文件初始状态基本有序(指正序),则应选用直接插入、冒泡 或随机的快速排序为宜
- 若 n 较大,则应采用 快速排序 、 堆排序 或归并排序 。
Arrays工具类的使用
import java.util.Arrays;
boolean equals(int a[],int b[]) | 判断两个数组是否相等 |
String toString(int a[]) | 输出数组信息 |
void fill(int a[],int x) | 将指定值填充到数组之中 |
void sort(int a[]) | 对数组进行排序 |
int binarySearch(int a[],int key) | 对排序后的数组进行二分法检索指定的值 |
面向对象
面向过程与面向对象
1、面向过程 (POP ) 与 面向对象(OOP)
- 面向过程, 强调的是功能 行为,以函数为最小单位,考虑怎么做 ;
- 面向对象,将功能封装进对象, 强调具备了功能 的对象,以类/对象为最小单位,考虑谁来做。
- 面向对象更加强调运用人类在日常的思维逻辑中采用的思想方法与原则,如抽象、分类、继承、聚合、多态等。
2、面向对象的三大特征
- 封装
- 继承
- 多态
将大象装进冰箱
思想概述
面向对象分析方法分析问题的思路和步骤
- 根据问题需要,选择问题所针对的 现实世界中的实体 。
- 从实体中寻找解决问题相关的属性和功能,这些属性和功能就形成了 概念世界中的类 。
- 把抽象的实体用计算机语言进行描述, 形成计算机世界中类的定义 。即借助某种程序语言,把类构造成计算机能够识别和处理的数据结构。
- 将 类实例化成计算机世界中的对象 。对象是计算机世界中解决问题的最终工具。
java语言的基本元素:类和对象
类 (Class) 和 对象 (Object) 是面向对象的核心概念。
- 类 是对 一类事物的描述 ,是 抽象的 、概念上的定义
- 对象 是 实际存在 的该类事物的每个个体,因而 也称为 实例 。
类的语法格式
修饰符 class 类名{
属性声明;
方法声明;
}
说明:
- 修饰符 “public” :类可以被任意访问
- 类的正文要用{ }括起来
对象
创建对象语法
类名 对象名 = new 类名();
使用“ 对象名 对象成员 ”的方式访问对象成员(包括属性和 方法)
实例
public class Zoo {
public static void main(String[] args) {//创建对象
Animal xb = new Animal;
xb.legs = 4;//访问属性
System.out.println(xb.legs);
xb.eat();//访问方法
xb.move();//访问方法
}
}
如果创建了一个类的多个对象,对于类中定义的属性,每个对象都拥有各自的一套副本,且互不干扰。
属性
语法格式
修饰符 数据类型 属性名 = 初始化值 ;
1、修饰符
- 常用的权限修饰符有: private 、缺省、 protected 、 public
- 其他修饰符: static
2 、数据类型
任何基本数据类型 (如 int 、 Boolean) 或 任何引用数据类型。
3 、属性名属于标识符,符合命名规则和规范即可。
成员变量与局部变量
成员变量 | 局部变量 | |
声明位置 | 在类中声明 | 方法形参或内部、代码块内、构造器内 |
修饰符 | private、public、static等 | 不能用权限修饰符修饰 |
初始化值 | 有默认初始化值 | 没有默认初始化值,必须显式赋值,方可使用 |
内存加载位置 | 堆空间或静态域 | 栈空间 |
方法
定义
- 方法 是类或对象行为特征的抽象,用来完成某个功能操作。在某些语言中也称为函数或过程。
- 将功能封装为方法的目的是,可以实现代码重用,简化代码
- Java 里的方法不能独立存在,所有的方法必须定义在类 里。
方法的声明格式
修饰符 返回值类型 方法名(参数类型 形参 1 , 参数 类型 形参 2,…… ){
方法体程序代码;
return 返回值;
}1、修饰符:
public, 缺省 ,private, protected 等
2、返回值类型:
- 没有返回值: void ;
- 有返回值,声明出返回值的 类型。与方法体中 return 返回值” 搭配使用
3、方法名:
属于标识符,命名时遵循标识符命名规则和规范 见名知意”
4、形参列表:
可以包含零个,一个或多个
5、参数。多个参数时,中间用“ ,”隔开
6、返回值:
方法在执行完毕后返还给调用它的程序的数据。
无返回值 | 有返回值 | |
无形参 | void 方法名(){} | 返回值类型 方法名(){} |
有形参 | void 方法名(形参列表){} | 返回值类型 方法名(形参列表){} |
注意
1、方法被调用一次,就会执行一次
2、没有具体返回值的情况,返回值类型用关键字 void 表示 ,那么方法体中可以不必使用 return 语句。如果使用,仅用来结束方法。
3、定义方法时,方法的结果应该返回给调用者,交由调用者 处理 。
4、方法中只能 调用 方法或属性, 不可以在方法内部定义方法。
传参
- 形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参
- 形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参
实例:定义一个 int 型的数组: {12,3,3,34,56,77,432};让数组的每个位置上的值去除以首位置的元素,得到的结果,作为该位置上的新值。遍历新的数组。
public class test3 {
public static void main(String[] args) {
int num[]={12,3,3,34,56,77,432};
int i;
for(i=num.length-1;i>0;i--){
num[i]/=num[0];
System.out.println(num[i]);
}
}
}
实例:求半径为min~max的圆的面积
import java.util.Scanner;
public class test4 {
static double print_Areas(int time) {
double area = time * time * 3.14;
return area;
}
public static void main(String[] args) {
int i, min, max;
System.out.println("please input min :>");
Scanner Min = new Scanner(System.in);
min = Min.nextInt();
System.out.println("please input max:>");
Scanner Max = new Scanner(System.in);
max = Max.nextInt();
double areas;
System.out.println("r\t\t\tarea\n");
for (i = min; i <= max; i++) {
areas = print_Areas(i);
System.out.println(i + "\t\t\t" + areas);
}
}
}
实例:求阶乘n!(使用递归)
import java.util.Scanner;
public class test5 {
static int jie_cheng(int n) {
if (n <= 1) {
return 1;
} else {
return (n * jie_cheng(n - 1));
}
}
public static void main(String[] args) {
Scanner N = new Scanner(System.in);
int n = N.nextInt();
int s = jie_cheng(n);
System.out.println(s);
}
}
封装与隐藏
封装的作用以及其含义
1、程序设计追求 “高内聚,低耦合”。
- 高内聚:类的内部数据 操作细节自己完成,不允许外部干涉;
- 低耦合:仅对外暴露少量的方法用于使用 。
2、隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性 。通俗的说 把该隐藏的隐藏起来,该暴露的暴露出来 。 这就是封装性的设计思想。
封装的原因
使用者对类内部定义的属性(对象的成员变量)的直接操作会导致数据的错误、混乱或安全性问题。
Java中通过将数据声明为私有的 (private) 再提供公共的(public)
方法 getXxx() 和 setXxx() 实现对该属性的操作 以实现下述目的:
- 隐藏 一个类中不需要对外提供的 实现细节;
- 使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑限制对属性的不合理操作;
- 便于修改 增强代码的可维护性;
修饰符 | 类内部 | 包内部 | 不同包的子类 | 同一个工程 |
private | yes | |||
(缺省) | yes | yes | ||
protected | yes | yes | yes | |
public | yes | yes | yes | yes |
注意
对于class 的权限修饰只可以用 public 和 default(缺省) 。
构造器(构造方法)
特征
- 它具有与类相同的名称
- 它不声明返回值类型。(与声明为 void 不同)
- 不能被 static 、 final 、 synchronized 、 abstract 、 native 修饰,不能有return 语句返回值
作用
- 创建对象
- 给对象进行初始化
语法格式
修饰符 类名 (参数列表){
初始化语句;
}
根据参数不同,构造器可以分为如下两类:
- 隐式无参构造器(系统 默认提供)
- 显式定义一个或多个构造器(无参、有参)
注 意
- Java 语言中,每个类都至少有一个 构造器
- 默认构造器的修饰符与所属类的修饰符一致
- 一旦 显式定义了 构造器, 则系统不再提供默认构造器
- 一个类可以创建多个重载的构造器
- 父类的构造器不可被子类继承
this
1、作用
- 它在方法内部使用,即这个方法所属对象的引用;
- 它在构造器内部使用,表示该构造器正在初始化的对象。
2、this 可以 调用类的属性、方法和构造器
3、使用情况
- 当在方法内需要用到调用该方法的对象时,就用 this .
- 具体的:我们可以用this 来 区分 属性 和 局部变量
- 比如:this.name = name
4、使用
- 在 任意 方法或构造器内 ,如果使用当前类的成员变量或成员方法可以在其前面添加this增强程序的阅读性。不过,通常我们都习惯省略 this 。
- 当形参与成员变量同名时,如果在方法 内或构造器内需要使用成员变量,必须添加 this 来表明该变量是类的成员 变量
- 使用 this 访问属性和方法时,如果在本类中未找到,会从父类中查找
- 可以作为一个类中构造器相互调用的特殊格式
这里引用一个大佬的解释this关键字
注意
- 可以 在类的构造器中使用 " this(形参列表)" 的方式,调用本类中重载的其他的构造器!
- 明确 :构造器中不能通过 " this(形参列表)" 的方式调用自身构造器
- 如果 一个类中声明了 n 个构造器,则最多有 n-1 个构造器中使用了" this(形参列表)“
- "this(形参列表)” 必须声明在类的构造器的首行!
- 在 类的一个构造器中,最多只能声明一个 " this(形参列表)”
关键字package、import
package
package 语句作为 Java 源文件的第一条语句,指明该文件中定义的类所在的包。 若缺省该语句,则指定为无名包 。它的格式为:package 顶层包名 子包名;
作用
- 包帮助管理大型软件 系统 将功能 相近的类划分到同一个包中 。 比如 MVC 的设计模式
- 包可以包含类和 子包 划分项目 层次 便于管理
- 解决类命名冲突的问题
- 控制访问权限
JDK中主要的包介绍
- java.lang 包含一些 Java 语言的核心类 如 String 、 Math 、 Integer 、 System 和Thread 提供常用功能
- java.net 包含执行与网络相关的操作的类和接口 。
- java.io 包含能提供多种输入 输出功能的类 。
- java.util 包含一些实用工具类 如定义系统特性、接口的集合框架 类 、使用与日期日历相关的函数
- java.text 包含了一些 java 格式化相关的类
- java.sql 包含了 java 进行 JDBC 数据库编程的相关类 接口
- java.awt 包含了构成抽象窗口工具集 abstract window toolkits 的多个类, 这些类被用来构建和管理应用程序的图形 用户 界面 (GUI) 。
import
为使用定义在不同包中的 Java 类,需用 import 语句来引入 指定包层次下 所需要的类
或全部类 。 import 语句告诉编译器到哪里去寻找类。语法格式:import 包名 . 类名;
注意
- 在源文件中使用 import 显式的导入指定包下的类或接口
- 声明 在包的声明和类的声明之间。
- 如果 需要导入多个类或接口,那么就并列显式多个 import 语句即可
- 举例 :可以使用 java.util.* 的方式,一次性导入 util 包下所有的类或接口。
- 如果 导入的类或接口是 java.lang 包下的,或者是当前包下的,则可以省略此 import 语句。
- 如果 在代码中使用不同包下的同名的类。那么就需要使用类的全类名的方式指明调用的是哪个类。
- 如果 已经导入 java.a 包下的类。那么如果需要使用 a 包的子包下的类的话,仍然需要 导入
- import static 组合的使用:调用指定类或接口下的静态的属性或方法
继承性
类继承语法规则
class Subclass extends SuperClass{}
作用
- 继承 的 出现减少了代码冗余,提高了代码的复用性。
- 继承的出现,更有利于功能的扩展。
- 继承 的出现让类与类之间产生了 关系 ,提供了多态的 前提 。
- 子类继承了父类,就继承了父类的方法和属性。
- 在子类中,可以使用父类中定义的方法和属性,也可以创建新的数据和方法。
- 在 Java 中,继承的关键字用的是“ extends ”,即子类不是父类的子集,而是对父类的“扩展” 。
规则
- 子类不能直接访问父类中私有的(private)的成员变量和方法
- JAVA只支持单继承和多层继承,不允许多重继承
0
class Person {//父类
public String name, sex;
public int age;
}
class Student extends Person {//子类,不仅可以调用父类中的name,sex,age,还新增了一个schol
public String school;
;
}
方法的重写
定义
在子类中可以根据需要对从父类中继承来的方法进行改造 也称为方法 的 重置、覆盖 。在程序执行时,子类的方法将覆盖父类的方法。
要求
- 子类重写的方法 必须 和父类被重写的方法 具有相同的 方法名称、 参数 列表
- 子类重写的方法的返回值类型 不能大于 父类被重写的方法的返回值类型
- 子类重写的方法使用的访问权限 不能小于 父类被重写的方法的访问权限
- 子类不能重写父类中声明为 private 权限的方法
- 子类方法抛出的异常不能大于父类被重写方法 的异常
注意
子类与父类中同名同参数的方法必须同时声明为非 static 的( 即为 重写 ),或者同时声明为static 的 不是 重写 。因为 static 方法是属于类的,子类无法覆盖父类的方法。
class Person {
public String name;
public int age;
public String getInfo() {
return "Name: " + name + " n" + "age: " + age;
}
}
class Student extends Person {
public String school;
public String getInfo() { //重写方法
return "Name: " + name + " \nage : " + age + " \nschool : " + school;
}
public static void main(String[] args) {
Student s1 = new Student();
s1.name = "Bob";
s1.age = 20;
s1.school = "school2";
System.out.println(s1.getInfo()); //Name:Bob age:20 school:school2
}
}
关键字:super
定义
调用父类中的指定操作:
- super 可用于访问父类中定义的属性
- super 可用于调用父类中定义的成员方法
- super 可用于在子类构造器中调用父类的构造器
注意
- 尤其 当子父类出现同名成员时 可以 用 super 表明调用的是父类中的成员
- super 的追溯不仅限于直接父 类
- super 和 this 的用法相像,this 代表本类对象的引用 super 代表父类的内存空间的标识
class Person {
protected String name = "张三";
protected int age;
public String getInfo() {
return "Name: " + name + "\nage: " + age;
}
}
class Student extends Person {
protected String name = "李四";
private String school = "New Oriental";
public String getSchool() {
return school;
}
public String getInfo() {
return super.getInfo() + "\nschool: " + school;
}
}
class StudentTest {
public static void main(String[] args) {
Student st = new Student();
System.out.println(st.getInfo());
}
}
区别 | this | super |
访问属性 | 访问本类中属性 如果本类没有此属性则从父类查找 | 直接访问父类中的属性 |
调用方法 | 访问本类中的方法 如果本类没有此方法则从父类查找 | 直接访问父类中的方法 |
调用构造器 | 调用本类构造器 必须放在构造器的首行 | 调用父类构造器 必须放在父类构造器的首行 |
子类对象实例化过程
class Creature {
public Creature() {
System.out.println("Creature 无参数的构造器");
}
}
class Animal extends Creature {
public Animal(String name) {
System.out.println("Animal 带一个参数的构造器,该动物的 name 为 " + name);
}
public Animal(String name, int age) {
this(name);
System.out.println("Animal 带两个参数的构造器,其 age 为 " + age);
}
}
class Wolf extends Animal {
public Wolf() {
super("灰太狼 ", 3);
System.out.println("Wolf 无参数的构造器");
}
public static void main(String[] args) {
new Wolf();
}
}
多态性
定义
Java 引用变量有两个类型: 编译时类型 和 运行时类型 。 编译时类型由声明该变量时使用的类型决定 运行时类型由实际赋给该变量的对象 决定 。 简称: 编译 时“看左边”;运行时“看右边” 。
若编译时类型和运行时类型不一致 就出现了对象的多态性
多态情况下
- “看左边”看的是父类的引用(父类中不具备子类特有的方法)
- “看右边”看的是子类的对象(实际运行的是子类重写父类的方法)
对象的多态
在 Java 中 子类 的对象可以替代 父类 的对象使用
- 一个变量只能有一种确定的数据类型
- 一个引用类型变量可能指向 引用 多种不同类型的对象
实例:方法声明的形参类型为 父类 类型,可以使用 子类的对象 作为实参调用 该 方法
class Test {
public void method(Person e) {
//....
e.getInfo();
}
public static void main(String args[]) {
Test t = new Test();
Student m = new Student();
t.method(m); // 子类的对象 m 传送给父类类型的参数 e
}
}
instanceof操作符
x instanceof A :检验 x 是否为类 A 的对象,返回值为 boolean 型。
- 要求 x 所属的类与类 A 必须是子类和父类的关系,否则编译错误。
- 如果 x 属于类 A 的子类 B x instanceof A 值也为 true 。
对象类型转换
1基本数据类型的对象类型转换
- 自动类型转换 :小的数据类型可以自动转换成大的数据类型
- 如long g= 20; double d= 12.0f
- 强制类型转换:可以把大的数据类型强制转换 ( 成小的数据类型)
- 如float f=(float) 12.0; int a=(int) 1200 L;
2、对 Java 对象的强制类型转换称为造型
- 从子类到父类的类型转换可以自动进行
- 从父类到子类的类型转换必须通过造型(强制类型转换)实现
- 无继承关系的引用类型间的转换是非法的
- 在造型前可以使用 instanceof 操作符测试一个对象的类型
class ConversionTest {
public static void main(String[] args) {
double d = 13.4;
long l = (long) d;
System.out.println(l);
int in = 5;
//boolean b=(boolean)in;
Object obj = "hello";
String objStr = (String) obj;
System.out.println(objStr);
Object objPri = new Integer(5);
//所以下面代码运行时引发 ClassCastException 异常
String str = (String) objPri;
}
}
class Test {
public void method(Person e) { // 设 Person 类中没有 getschool() 方法
//System.out.pritnln(e.getschool()); // 非法 编译时错误
if (e instanceof Student) {
Student me = (Student) e; // 将 e 强制转换为 Student 类型
System.out.println(me.getschool());
}
}
public static void main(String[] args) {
Test t = new Test();
Student m = new Student();
t.method(m);
}
}
子类继承父类
- 若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子 类 中。
- 对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量
实例:建立InstanceTest 类,在类中定义方法method(Person e);
在method 中
(1)根据 e 的类型调用相应类的 getInfo 方法。
(2)根据 e 的类型执行:
- 如果e 为 Person 类的对象,输出“a person”
- 如果e 为 Student 类的对象 输出:“a student”,“a person ”
- 如果e 为 Graduate 类的对象,输出:“a graduated student”,“a student”,“a person”
class Person {
protected String name = "person";
protected int age = 50;
public String getInfo() {
return "Name:" + name + "\n" + "age:" + age;
}
}
class Student extends Person {
protected String school = "pku";
public String getInfo() {
return "Name:" + name + "\n" + "age:" + age + "\nschool:" + school;
}
}
class Graduate extends Student {
public String major = "IT";
public String getInfo() {
return "Name:" + name + "\n" + "age:" + age + "\nschool:" + school + "\nmajor:" + major;
}
}
class InstanceTest {
public static void main(String[] args) {
//InstanceTest m=new ;
InstanceTest m = new InstanceTest();
//Person n=new Person();
m.method(new Graduate());
}
public void method(Person e) {
String a = e.getInfo();
System.out.println(a);
//方式一:
// if(e instanceof Graduate){
// System.out.println("a graduate student"+"\na student"+"\na person");
// }else if(e instanceof Student){
// System.out.println("a student"+"\na person");
// }else{
// System.out.println("a person");
// }
//方式二:
if (e instanceof Graduate) {
System.out.println("a graduate student");
}
if (e instanceof Student) {
System.out.println("a student");
}
if (e instanceof Person) {
System.out.println("a person");
}
}
}
实例:定义三个类,父类 GeometricObject 代表几何形状,子类 Circle 代表圆形, MyRectangle 代表矩形 。
定义一个测试类 GeometricTest 编写 equalsArea 方法测试两个对象的面积是否相等(注意方法的参数类型,利用动态绑定技术),编写 displayGeometricObject 方法显示对象的面积(注意方法的参数类型,利用动态绑定技术)。
class GeometricObject {
protected String color;
protected double weight;
protected GeometricObject() {
}
protected GeometricObject(String color, double weight) {
this.color = color;
this.weight = weight;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
public double findArea() {
return 0.0;
}
}
class Circle extends GeometricObject {
private double radius;
public Circle(double radius, String color, double weight) {
super(color, weight);
this.radius = radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
public double findArea() {
return Math.PI * radius * radius;
}
}
class MyRectangle extends GeometricObject {
private double width;
private double height;
public MyRectangle(double width, double height, String color, double weight) {
super(color, weight);
this.width = width;
this.height = height;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public double findArea() {
return width * height;
}
}
class GeometricTest {
public static void main(String[] args) {
GeometricTest test = new GeometricTest();
MyRectangle r = new MyRectangle(1, 1, "red", 1);
Circle c = new Circle(2.3, "yellow", 1.0);
Circle c1 = new Circle(2.3, "yellow", 1.0);
test.displayGeometricObject(c);
test.displayGeometricObject(r);
boolean isEqual = test.equalsArea(c1, c);
System.out.println("面积是否相等?" + isEqual);
}
public boolean equalsArea(GeometricObject a, GeometricObject b) {
return a.findArea() == b.findArea();
}
public void displayGeometricObject(GeometricObject g) {
double a1 = g.findArea();
System.out.println("面积为" + a1);
}
}
Object 类
说明
- Object 类是所有 Java 类的根父类
- 如果在类的声明中未使用 extends 关键字指明其父类,则默认父类为 java lang Object 类
主要结构
方法名称 | 类型 | 描述 |
public Object() | 构造 | 构造器 |
public boolean equals(Object obj) | 普通 | 对象比较 |
public int hashCode() | 普通 | 取得Hash码 |
public String toString() | 普通 | 对象打印时调用 |
==
- 基本类型比较值:只要两个变量的值相等 即 为 true 。
int a=5; if (a==6)//flase {}
- 引用类型比较引用(是否指向同一个 对象 ):只有 指向同一个对象时 才
- 返回 true 。
Person p1=new Person(); Person p2=new Person(); if(p1==p2)//ture {}
用 进行比较时 符号两边的 数据类型必须兼容(可自动转换的基本数据类型除外) 否则编译出错
equals()方法
只能比较引用类型 其作用与“==”相同 比较是否指向同一个对象 。(注意,此方法可以重写,这是此方法原本的定义)
Person p1=new Person(); Person p2=new Person(); p2.equals(p1)//ture
重写equals()方法原则
- 1、对称性
- 如果 x.quals(y) 返回是 true 那么 y.equals(x) 也应该返回是true 。
- 2、自反性
- x.equals(x) 必须返回是 true 。
- 3、传递性
- 如果x.equals(y) 返回是 true 而且y.equals(z) 返回是true;那么 z.equals(x) 也应该返回是 true 。
- 4、一致性
- 如果x.equals(y) 返回是 true 只要 x 和 y 内容一直不变 不管你重复 x.equals(y) 多少次 返回都是 true 。
- 5、任何情况下 x.equals(null) 永远返回是 false;x.equals( 和 x 不同类型的对象)永远返回是 false 。
实例:验证“==”与equals()方法差别
public static void main(String[] args) {
int it = 65;
float fl = 65.0f;
System.out.println(it == fl);//ture
char ch1 = 'A';
char ch2 = 12;
System.out.println(it == ch1);//ture
System.out.println(12 == ch2);//ture
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println(str1 == str2);//false
System.out.println(str1.equals(str2));//ture
System.out.println("hello" == new java.util.Date());//编译不通过
}
实例:编写代码并且覆盖equals方法比较两个MyDate对象的年月日
class MyDateTest {
public static void main(String[] args) {
MyDate m1 = new MyDate(14, 3, 1976);
MyDate m2 = new MyDate(14, 3, 1976);
if (m1 == m2) {
System.out.println("m1==m2");
} else {
System.out.println("m1!=m2"); // m1 != m2
}
if (m1.equals(m2)) {
System.out.println("m1 is equal to m2");// m1 is equal to m2
} else {
System.out.println("m1 is not equal to m2");
}
}
}
class MyDate {
private int day;
private int month;
private int year;
public MyDate(int day, int month, int year) {
super();
this.day = day;
this.month = month;
this.year = year;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
@Override//将方法覆盖(重写)
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof MyDate) {
MyDate myDate = (MyDate) obj;
return this.day == myDate.day && this.month == myDate.month &&
this.year == myDate.year;
}
return false;
}
}
toString()方法
- toString 方法在 Object 类中定义 其返回值是 String 类型 返回类名和它的引用地址 。
- 在进行 String 与其它类型数据的连接操作时 自动调用 toString() 方法
Date now=new Date(); System out println ("now="+now); //相当于System out println ("now="+now.toString());
- 基本类型数据转换为 String 类型时 调用了对应包装类的 toString 方法
int a=10; System.out.peintln("a="+a);
实例:定义两个类,父类 GeometricObject 代表几何形状,子类 Circle 代表圆形。
写一个测试类,创建两个Circle 对象,判断其颜色是否相等;利用 equals 方法判断其半径是否相等;利用toString 方法输出其半径。(转自这篇equals方法)
class GeometricObject {
protected String color;
protected double weight;
protected GeometricObject() {
color = "white";
weight = 1.0;
}
public GeometricObject(String color, double weight) {
this.color = color;
this.weight = weight;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
}
class Circle extends GeometricObject {
private double radius;
public Circle() {
super();
radius = 1.0;
}
public Circle(double radius) {
super();
this.radius = radius;
}
public Circle(String color, double weight, double radius) {
super(color, weight);
this.radius = radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
//求圆的面积
public double findArea(double radius) {
return Math.PI * radius * radius;
}
// 简单重写equals用来比较两个圆的半径radius是否相等,如相等,返回true。
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof Circle) {//判断obj是否是Circle的实例
Circle circle = (Circle) obj;
return this.radius == circle.radius;
}
return false;
}
// @Override //也可使用系统自动生成的比较半径radius的equals方法,也可自己简单重写,熟悉equals原理
// public boolean equals(Object o) {
// if (this == o) return true;
// if (!(o instanceof Circle)) return false;
// Circle circle = (Circle) o;
// return Double.compare(circle.getRadius(), getRadius()) == 0;
// }
@Override
public String toString() {
return "{ radius=" + radius + " }";
}
}
/*
写一个测试类,创建两个Circle对象,判断其颜色是否相等;利用equals方法判断其半径是否相等;利用
toString()方法输出其半径。
*/
class GeometricObjectTest {
public static void main(String[] args) {
//Circle circle6=new Circle(2.0);//这样定义初始化此时color和weight为GeometricObject中的默认值
Circle circle = new Circle("white", 1.0, 2.0);
Circle circle1 = new Circle("black", 1.0, 3.0);
Circle circle2 = new Circle("white", 1.0, 3.0);
System.out.println("circle和circle1两个圆的颜色是否相同: " + circle.getColor().equals(circle1.getColor()));//此时调用java.lang.String包下的默认equals方法
System.out.println("circle和circle2两个圆的颜色是否相同: " + circle.getColor().equals(circle2.getColor()));//此时调用java.lang.String包下的默认equals方法
System.out.println("circle和circle2两个圆的半径是否相同: " + circle.equals(circle2));//此时调用Circle类中重写的equals方法
System.out.println("circle半径为: " + circle.toString());//调用为Circle重写的toString()方法
System.out.println("circle1半径为: " + circle1.toString());
System.out.println("circle2半径为: " + circle2.toString());
}
}
包装类
- 针对 八 种 基本数据类型定义 相应的引用类型——包装类(封装类)
- 有了类的特点,就可以调用类中的方法, Java 才是真正的面向对象
基本数据类型 | 包装类 |
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Character |
基本 数据类型包装成包装类的实例——装箱
- 通过包装类的构造器实现:
int i=500; Integer t=new Integer(i);
- 还可以通过字符串参数构造包装类对象:
Float f=new Float("4.56"); Long I=new Long("asdf");
获得 包装类对象中包装的基本类型变量——拆箱
- 调用包装类的 xxxValue 方法:
boolean=bObj.boleanValue();
字符串转换成基本数据类型
- 通过包装类的构造器实现:
int i=new Integer("12");
- 通过包装类的 parseXxx (String S)静态方法:
Float f=Float.parseFloat("12.1");
基本数据类型转换成字符串
- 调用字符串重载的 valueOf 方法:
String fstr=String.valueOf(2.34f);
- 更直接的方式:
String intStr=5+"";
实例:利用Vector 代替数组处理:从键盘读入学生成绩(以负数代表输入结束),找出最高分,并输出学生成绩等级。
import java.util.*;
public class test1 {
}
class WorkVector {
public static void main(String[] args) {
Vector c=new Vector();
int n=0;
int max=0;
System.out.println("请输入学生成绩:");
System.out.println("提示:若输出为负数则录入结束");
while(n>=0){
Scanner input=new Scanner(System.in);
n=input.nextInt();
if(n>=0&&n<=100) {
c.addElement(n);
}
}
System.out.println(c.size());
for(int i=0;i<c.size();i++) {
if(((int)c.elementAt(i))>max) {
max=(int)c.elementAt(i);
}
System.out.println(c.elementAt(i));
}
System.out.println("最高分为:"+max);
for(int i=0;i<c.size();i++) {
if(max-((int)c.elementAt(i))<=10) {
System.out.println(c.elementAt(i)+"\tA等");
}else if(max-((int)c.elementAt(i))<=20) {
System.out.println(c.elementAt(i)+"\tB等");
}else if(max-((int)c.elementAt(i))<=30) {
System.out.println(c.elementAt(i)+"\tC等");
}else {
System.out.println(c.elementAt(i)+"\tD等");
}
}
}
}
static
使用 范围:
在 Java 类中 可用 static 修饰 属性 、 方法 、 代码块 、 内部类
被修饰后的成员具备以下特点:
- 随着类的加载而加载
- 优先于对象存在
- 修饰的成员,被所有对象所共享
- 访问权限允许时,可不创建对象,直接被类调用
class Circle {
private double radius;
public static String name = "这是一个圆";
public static String getName() {
return name;
}
public Circle(double radius) {
this.radius = radius;
}
public double findArea() {
return Math.PI * radius * radius;
}
public void display() {
System.out.println("name=" + name + "radius:" + radius);
}
}
public class StaticTest {
public static void main(String[] args) {
Circle c1 = new Circle(2.0);
Circle c2 = new Circle(3.0);
c1.display();
c2.display();
}
}
注意
因为不需要实例就可以访问 static 方法,因此 static 方法内部不能有 this 和super
class Person {
private int id;
private static int total = 0;
public static void setTotalPerson(int total) {
this.total = total;// 非法,在 static 方法中不能有 this ,也不能有 super
}
public Person() {
total++;
id = total;
}
}
class PersonTest {
public static void main(String[] args) {
Person.setTotalPerson(3);
}
}
单例设计模式
所谓 类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类 只能存在一个对象实例 ,并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类 的 构造器 的 访问权限设置为 private ,这样,就不能用 new 操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能 调用该类的某个静态方法 以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的 该类对象的变量也必须定义成静态的 。
优点
由于单例模式只生成一个实例,减少了系统性能开销 ,当一个对象的产生 需要比较 多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用 启动时 直接产生一个单例对象,然后永久驻留内存的方式来解决。
- 饿汉式这种方式加载类对象,称作:预先加载方式。
- 懒汉式这种方式加载类对象,称作:延迟加载方式。
//饿汉式 class Singleton { //1.私有化构造器 private Singleton() { } //2.内部提供一个当前类的实例 //4.此实例也必须静态化 private static Singleton single = new Singleton(); //3.提供公共的静态的方法,返回当前类的对象 public static Singleton getInstance() { return single; } } //懒汉式 class Singleton { //1. 私有化构造器 private Singleton() { } //2. 内部提供一个当前类的实例 //4. 此实例也必须静态化 private static Singleton single; //3. 提供公共的静态的方法,返回当前类的对象 public static Singleton getInstance() { if (single == null) { single = new Singleton(); } return single; } }
代码块
非静态代码块:没有 static 修饰的代码块
- 可以 有输出语句 。
- 可以 对类的属性 、 类的声明进行初始化操作 。
- 除了调用非静态的结构外 还可以调用 静态的变量或方法 。
- 若 有多个非静态的代码块 那么按照从上到下的 顺序 依次 执行 。
- 每次 创建对象的时候 都会执行一次 。 且先于构造 器 执行
静态代码块:用 static 修饰的代码块
- 可以 有输出语句。
- 可以 对类的属性、类的声明进行初始化操作。
- 不可以对非静态的属性初始化。即:不可以调用非静态 的 属性 和方法。
- 若 有多个静态的代码块,那么按照从上到下的顺序依次执行。
- 静态 代码块的执行要先于非静态代码块。
- 静态 代码 块随着类的加载而加载,且只执行 一 次。
final
特点
- final 标记的类不能被继承 。 提高安全性提高程序的可读性
- final 标记的方法不能被子类重写。
- final 标记的变量 成员变量或局部变量 即称为常量 。 名称大写且只能被赋值一次 。
- final 标记的成员变量必须在声明时或 在 每个 构造器中 或代码块中显式赋值 然后才能使用 。
用法
1. final 修饰类
中国古代,什么人不能有后代,就可以被final 声明 称为“太监类”!final class A { } class B extends A {//编译错误,不能被继承 }
2.final修饰方法
class A { public final void print() { System.out.println("A"); } } class B extends A { public void print() { // 错误,不能被重写。 System.out.println(" 尚硅谷"); } }
3. final 修饰变量——常量(static final:全局常量)
常量名要大写,内容不可修改。如同古代皇帝的圣旨。class A { private final String INFO = " atguigu";// 声明常量 public void print() { //The final field A.INFO cannot be assigned //INFO = " 尚硅谷"; } }
实例
final class Test {
public static int totalNumber = 5;
public final int ID;
public Test() {
ID = totalNumber; // 可在 构造 器 中 给 final 修饰的“变量”赋值
}
public static void main(String[] args) {
Test t = new Test();
System.out.println(t.ID);
final int I = 10;
final int J;
J = 20;
J = 30; // 非法
}
}
抽象类与抽象方法
定义与用法
- 用 abstract 关键字来修饰一 个 类, 这个类叫做 抽象 类 。
- 用 abstract 来修饰一 个 方法, 该方法叫做 抽象方法 。抽象方法:只有方法的声明,没有方法的实现。以分号 结束:比如:public abstract void talk()
- 含有抽象方法的类必须被声明为抽象类。
- 抽象类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。
- 不能 用 abstract 修饰变量、代码块、构造器;
- 不能用 abstract 修饰 私有方法、静态方法、 final 的 方法、 final 的类。
- abstract定义的static为外部类
abstract class A {
abstract void m1();
public void m2() {
System.out.println("A 类中定义的 m2 方法");
}
}
class B extends A {
void m1() {
System.out.println("B 类中定义的 m1 方法'");
}
}
class Test{
public
static void main(String args[]) {
A a = new B();
a.m1();
a.m2();
}
}
abstract class A{
//static定义的内部类属于外部类
static abstract class B{
public abstract void print();
}
}
class C extends A.B{
public void print(){
System.out.println("**********");
}
}
public class TestDemo {
public static void main(String[] args) {
//向上转型
A.B ab = new C();
ab.print();
}
}
应用
抽象类是用来模型化那些父类无法确定全部实现,而是由其子类提供具体实现的对象的类。
实例:在航运公司系统中,Vehicle 类需要定义两个方法分别计算运输工具的燃料效率和行驶距离
问题:卡车和驳船 的燃料效率和行驶距离的计算方法完全不同。 Vehicle 类不能提供计算方法,但子类可以。
public abstract class Vehicle {
public abstract double calcFuelEfficiency();// 计算燃料效率的抽象方法
public abstract double calcTripDistance();//计算行驶距离的抽象方法
}
class Truck extends Vehicle {
public double calcFuelEfficiency() { //写出计算卡车的燃料效率的具体方法
public double calcTripDistance () { //写出计算卡车行驶距离的具体方法
}
}
public class RiverBarge extends Vehicle {
public double calcFuelEfficiency() {//写出计算驳船的燃料效率的具体方法
}
public double calcTripDistance() {//写出计算驳船行驶距离的具体方法
}
}
}
多态的应用:模板方法设计模式(TemplateMethod)
解决的问题
- 当功能内部一部分实现 是 确定 的 一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去 实现 。
- 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式
abstract class Template {
public final void getTime() {
long start = System.currentTimeMillis();
code();
long end = System.currentTimeMillis();
System.out.println("执行时间是: " + (end - start));
}
public abstract void code();
}
class SubTemplate extends Template {
@Override
public void code() {
for (int i = 0; i < 10000; i++) {
System.out.println(i);
}
}
}
模板方法设计模式 是编程中经常用得到的模式 。 各个 框架 、 类库中都有他的影子,比如常见的有:
- 数据库访问的封装
- Junit 单元测试
- JavaWeb 的 Servlet 中关于 doGet/doPost 方法调用
- Hibernate 中模板程序
- Spring 中 JDBCTemlate 、 HibernateTemplate 等
实例:编写工资系统,实现不同类型员工(多态)的按月发放工资。如果当月出现某个
Employee对象的生日,则将该雇员的工资增加100元。(取自工资系统)
import java.util.Scanner;
class SalarySystem {
public static void main(String[] args) {
Employee employee[] = new Employee[4];
Scanner scan = new Scanner(System.in);
for (int i = 0; i < 2; i++) {
System.out.print("输入小时职员姓名:");
String name = scan.next();
System.out.print("编号:");
int number = scan.nextInt();
System.out.print("出生年、月、日:");
int year = scan.nextInt();
int month = scan.nextInt();
int day = scan.nextInt();
System.out.print("时薪:");
int wage = scan.nextInt();
System.out.print("工作时长:");
int hour = scan.nextInt();
employee[i] = new HourlyEmployee(name, number, new MyDate(year, month, day), wage, hour);
System.out.println();
}
for (int i = 2; i < 4; i++) {
System.out.print("输入普通职员姓名:");
String name = scan.next();
System.out.print("编号:");
int number = scan.nextInt();
System.out.print("出生年、月、日:");
int year = scan.nextInt();
int month = scan.nextInt();
int day = scan.nextInt();
System.out.print("月薪:");
int monthlySalary = scan.nextInt();
employee[i] = new SalariedEmployee(name, number, new MyDate(year, month, day), monthlySalary);
System.out.println();
}
//遍历并同时为该月生日的员工增加工资
String name[] = new String[5];//用来存放当月生日员工的名单
int count = 0;//遇当月生日员工个数
boolean isbirthday = false;//当月有无员工过生日
System.out.print("输入当月月份:");
int m = scan.nextInt();
System.out.println("员工类型\t\t\t\t姓名\t编号\t出生日期\t\t工资");
for (int i = 0; i < employee.length; i++) {
if (m == employee[i].getBirthday().getMonth()) {
employee[i].birthdaySalaryAdd();
name[count] = employee[i].getName();
count++;
isbirthday = true;
}
System.out.println(employee[i] + "\t" + employee[i].earnings());//对象引用直接调用重写父类的toString()
}
if (isbirthday) {
System.out.print("当月");
for (int i = 0; i < count; i++) {
System.out.print(name[i] + " ");
}
System.out.println("过生日,已为其增加工资100元");
}
}
}
abstract class Employee {
private String name;
private int number;
private MyDate birthday;
public Employee(String name, int number, MyDate birthday) {
this.name = name;
this.number = number;
this.birthday = birthday;
}
public abstract int earnings();
public abstract void birthdaySalaryAdd();//由于不同员工的工资表示形式有所差异,员工遇生日需增加100元由子类做具体实现
@Override
public String toString() {
return name + "\t" + number + "\t" + birthday.toDateString();
}
public String getName() {
return name;
}
public int getNumber() {
return number;
}
public MyDate getBirthday() {
return birthday;
}
}
class SalariedEmployee extends Employee {
private int monthlySalary;
public SalariedEmployee(String name, int number, MyDate birthday, int monthlySalary) {
super(name, number, birthday);
this.monthlySalary = monthlySalary;
}
@Override
public void birthdaySalaryAdd() {
int newSalary = monthlySalary + 100;
this.setMonthlySalary(newSalary);
}
public void setMonthlySalary(int monthlySalary) {
this.monthlySalary = monthlySalary;
}
@Override
public int earnings() {
return monthlySalary;
}
@Override
public String toString() {
return "SalariedEmployee\t" + super.toString();
}
}
class HourlyEmployee extends Employee {
private int wage;
private int hour;
private int hourlySalary;
public HourlyEmployee(String name, int number, MyDate birthday, int wage, int hour) {
super(name, number, birthday);
this.wage = wage;//每小时的工资
this.hour = hour;//月工作的总小时数
this.setHourlySalary(wage * hour);
}
public void setHourlySalary(int hourlySalary) {
this.hourlySalary = hourlySalary;
}
@Override
public void birthdaySalaryAdd() {
int newSalary = hourlySalary + 100;
this.setHourlySalary(newSalary);
}
@Override
public int earnings() {
return hourlySalary;
}
@Override
public String toString() {
return "HourlyEmployee\t\t" + super.toString();
}
}
class MyDate {
private int year;
private int month;
private int day;
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public String toDateString() {
return year + "年" + month + "月" + day + "日";
}
public int getMonth() {
return month;
}
}
接口
接口 interface 是 抽象方法 和 常量值 定义的集合 。
特点
- 用 interface 来定义 。
- 接口中的所有成员变量都 默认 是由 public static final 修饰的 。
- 接口中 的所有抽象方法 都 默认 是由 public abstract 修饰的 。
- 接口中没有 构造器 。
- 接口采用多继承机制 。
- 一个类可以有多个无关的接口
举例
语法格式
先写 extends ,后写 implements
举例
interface Runner {
public void start;
public void runpublic;
void stop();
}
class Person implements Runner {
public void start() {
//准备工作:弯腰、蹬腿、咬牙、瞪眼
//开跑
}
public void run() {
//摆动手臂
//维持直线方向
}
public void stop() {
//减速直至停止、喝水。
}
}
代理模式
应用场景:
- 安全代理: 屏蔽对真实角色的直接访问 。
- 远程代理: 通过代理类处理远程方法调用 RMI
- 延迟加载: 先加载轻量级的代理对象 真正需要再加载真实 对象。比如你要开发一个大文档查看软件大文档中有大的图片 有可能一个图片有100 MB 在打开文件时 不可能将所有的图片都显示出来 这样就可以使用代理模式 当需要查看图片时 用 proxy 来进行大图片的打开 。
分类
- 静态代理 静态定义代理类
- 动态代理 动态生成代理类
区别 | 抽象类 | 接口 |
定义 | 包含抽象方法的类 | 主要是抽象方法和全局常量的集合 |
组成 | 构造方法、抽象方法、普通方法、 常量、变量 | 常量、抽象方法、默认方法、 静态方法 |
使用 | 子类可以继承抽象类 | 子类实现接口 |
关系 | 抽象类可以实现多个接口 | 接口不能继承抽象类 但是可以击沉多个接口 |
常见设计模式 | 模板方法 | 简单工厂、工厂方法、代理模式 |
对象 | 都通过对象的多态性产生实例化对象 | |
局限 | 抽象类只能单继承 | 接口无局限 |
实际 | 作为一个模板 | 作为一个标准或一种能力 |
选择 | 如果二者都可以使用,那么优先使用接口 |
实例:定义一个接口用来实现两个对象的比较(转自接口练习)
/*
* 定义一个Circle类,声明radius属性,提供getter和setter方法
*/
class Circle {
private Double radius;
public Double getRadius() {
return radius;
}
public void setRadius(Double radius) {
this.radius = radius;
}
public Circle() {
super();
}
public Circle(Double radius) {
super();
this.radius = radius;
}
}
/*
* 定义一个ComparableCircle类,继承Circle类并且实现CompareObject接口。
* 在ComparableCircle类中给出接口中方法compareTo的实现体,用来比较两个圆的半径大小。
*/
class ComparableCircle extends Circle implements CompareObject {
public ComparableCircle(double radius) {
super(radius);
}
@Override
public int compareTo(Object o) {
if (this == o) {
return 0;
}
if (o instanceof ComparableCircle) {
ComparableCircle c = (ComparableCircle) o;
//错误的:
// return (int) (this.getRadius() - c.getRadius());
//正确的方式一:
// if(this.getRadius() > c.getRadius()){
// return 1;
// }else if(this.getRadius() < c.getRadius()){
// return -1;
// }else{
// return 0;
// }
//当属性radius声明为Double类型时,可以调用包装类的方法
//正确的方式二:
return this.getRadius().compareTo(c.getRadius());
} else {
// return 0;
throw new RuntimeException("传入的数据类型不匹配");
}
}
}
/*
* interface CompareObject{
public int compareTo(Object o);
//若返回值是 0 , 代表相等; 若为正数,代表当前对象大;负数代表当前对象小
}
*/
interface CompareObject {
//若返回值是 0 , 代表相等; 若为正数,代表当前对象大;负数代表当前对象小
public int compareTo(Object o);
}
class ComparableCircleTest {
public static void main(String[] args) {
ComparableCircle c1 = new ComparableCircle(3.4);
ComparableCircle c2 = new ComparableCircle(3.6);
int compareValue = c1.compareTo(c2);
if (compareValue > 0) {
System.out.println("c1对象大");
} else if (compareValue < 0) {
System.out.println("c2对象大");
} else {
System.out.println("c1与c2一样大");
}
int compareValue1 = c1.compareTo(new String("AA"));
System.out.println(compareValue1);
}
}
内部类
定义
- 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类 。
- 在 Java 中,允许一个类的定义位于另一个类的内部,前者称为 内部类 ,后者称为 外部类 。
- Inner class 一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称。
- Inner class 的名字不能与包含 它 的 外部类 类 名相同;
分类
- 成员内部类(static和非static)
- 局部内部类(不谈修饰符)
- 匿名内部类
1.成员内部类
(1)成员内部类作为 类 的 成员的角色:
- 和 外部类不同 Inner class 还可以声明 为 private 或 protected
- 可以调用外部类的结构
- Inner class 可以声明为 static 的 但此时就不能再使用外层类的非 static 的成员变量;
(2)成员内部类 作为类的角色:
- 可以在内部定义属性 、 方法 、 构造器等结构
- 可以 声明为 abstract 类 因此可以被其它的内 部类 继承
- 可以声明为 final 的
- 编译以后生成 OuterClass InnerClass class 字节码文件 也适用于局部内部类
(3)注意
- 非 static 的成员内 部类中的成员不能声明为 static 的 只有在外部类或 static 的成员内 部类中才可声明 static 成员 。
- 外部类 访问 成员 内 部类的成员 需要 内部类 成员 或 内部类对象 成员 的 方式
- 成员内部类可以直接使用外部类的所有成员 包括私有的数据
- 当想要在外部类的静态成员部分使用内部 类时 可以考虑内部类 声明为静态的
举例
class Outer {
private int s;
public class Inner {
public void mb() {
s = 100;
System.out.println("在内部类 Inner 中 s = " + s);
}
}
public void ma() {
Inner i = new Inner();
i.mb();
}
}
class InnerTest {
public static void main(String args[]) {
Outer o = new Outer();
o.ma();
}
}
class Outers {
private int s = 111;
public class Inner {
private int s = 222;
public void mb(int s) {
System.out.println(s); // 局部变量 s
System.out.println(this.s); // 内部类对象的属性 s
System.out.println(Outers.this.s); // 外部类对象属性 s
}
}
public static void main(String args[]) {
Outers a = new Outers();
Outers.Inner b = a.new Inner();
b.mb(333);
}
}
2.局部内部类
(1)格式
class 外部类 { 方法() { class 局部内部类 { } } { class 局部内部类 { } } }
(2)特点
- 内 部类仍然是一个独立的类,在编译之后内部类会被编译成独立的 .class 文件,但是前面冠以外部类的类名和 符号,以及数字编号。
- 只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类。
- 局部内部类可以使用外部类的成员,包括私有 的 。
- 局部内部类可以使用外部方法的局部变量,但是必须是 final 的。 由局部内部类和局部变量的声明周期不同所致。
- 局部内部类和局部变量地位类似,不能使用 public,protected, 缺省 ,private
- 局部内部类不能使用 static 修饰,因此也不能包含静态 成员
3.匿名内部类
(1)定义
匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例。一个匿名内部类一定是在 new 的后面,用其隐含实现一个接口或实现一个类。
(2)格式
new父类构造器(实参列表)|实现接口(){ //匿名内部类的类体部分 }
(3)特点
- 匿名内部类必须继承父类或实现接口
- 匿名内部类只能有一个对象
- 匿名内部类对象只能使用多态形式引用
举例
interface A {
public abstract void fun1();
}
class Outer {
public static void main(String[] args) {
new Outer().callInner(new A() {
//接口是不能 new 但此处比较特殊是子类对象实现接口,只不过没有为对象取名
@Override
public void fun1() {
System.out.println("implement for fun1");
}
});// 两步写成一步了
}
public void callInner(A a) {
a.fun1();
}
}
class Test {
public Test() {
Inner s1 = new Inner();
s1.a = 10;
Inner s2 = new Inner();
s2.a = 20;
Test.Inner s3 = new Test.Inner();
System.out.println(s3.a);
}
class Inner {
public int a = 5;
}
public static void main(String[] args) {
Test t = new Test();
Inner r = t.new Inner();
System.out.println(r.a);//输出为5 5
}
}
异常
异常概述与异常体系结构
定义
在 Java 语言中 将程序执行中发生的不正常情况称为异常 。开发过程中的语法错误和逻辑错误不是异常
异常事件分类
1、Error
Java 虚拟机无法解决的严重问题 。 如:
- JVM 系统内部错误
- 资源耗尽等严重 情况 。
一般 不编写针对性的代码进行处理 。
2、Exception
其它因编程错误或偶然的外在因素导致的一般性问题 可以使用针对性的代码进行处理。如:
- 空指针访问
- 试图读取不存在的文件
- 网络连接中断
- 数组角标越界
异常分类
1.运行时异常
- 是指编译器不要求强制处置的异常。一般是指编程时的逻辑错误,是程序员应该积极避免其出现的异常。 java.lang.RuntimeException 类及它的子类都是运行时异常。
- 对于这类异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。
2.编译时异常
- 是指编译器要求必须处置的异常。即程序在运行时由于外界因素造成的一般性异常。 编译器 要求 Java 程序必须捕获或声明所有编译时异常。
- 对于这类异常,如果程序不处理,可能会带来意想不到的结果。
常见异常
ArrayIndexOutOfBoundsException
class IndexOutExp {
public static void main(String[] args) {
String friends[] = {"lisa", "bily", "kessy" };
for (int i = 0; i < 5; i++) {
System.out.println(friends[i]); // friends[4]?
}
System.out.println("\nthis is the end");
}
}
运行结果
NullPointerException
class NullRef {
int i = 1;
public static void main(String[] args) {
NullRef t = new NullRef();
t = null;
System.out.println(t.i);
}
}
运行结果
ArithmeticException
class DivideZero {
int x;
public static void main(String[] args) {
int y;
DivideZero c = new DivideZero();
y = 3 / c.x;
System.out.println("program ends ok!");
}
}
运行结果
异常处理机制1:try-catch-finally(捕获)
简述
在编写程序时,经常要在可能出现错误的地方加上检测的代码,如进行 x/y 运算时,要 检测分母为 0 ,数据为空,输入的不是数据而是字符 等。 过多 的 if else 分支 会导致程序的 代码 加长、臃肿,可读性 差。因此 采用 异常处理机制 。
格式
try{
......
可能产生异常的代码
}
catch(ExceptionName1 e){
......
当产生 ExceptionName1 型异常时的处置措施
}
catch(ExceptionName2 e){
......
当产生 ExceptionName2 型异常时的处置措施
}
[
finally{
......
无论是否发生异常 都无条件执行的语句
}]
解释
1.try
- 捕获异常的第一步是用try{ 语句块选定捕获异常的范围 将可能出现异常的代码放在 try 语句块中 。
2.catch (Exceptiontype e)
- 在catch 语句块中是对 异常对象 进行处理的代码 。 每个 try 语句块可以伴随一个或 多个 catch 语句 用于处理可能产生的 不同类型 的异常对象 。
3.finally
- 捕获异常的最后一步是通过 finally 语句为异常处理提供一个统一的出口,使得在控制流转到程序的其它部分以前,能够对程序的状态作统一的管理。
- 不论在 try 代码块中是否发生了异常事件, catch 语句是否执行, catch 语句是否有异常, catch 语句中是否有 return。finally 块中的语句都会被执行。
- finally 语句和 catch 语句是任选的
举例
class IndexOutExp {
public
static void main(String[] args) {
String friends[] = {"lisa", "bily", "kessy" };
try {
for (int i = 0; i < 5; i++) {
System.out.println(friends[i]);
}
} catch (
ArrayIndexOutOfBoundsException e) {
System.out.println("index err");
}
System.out.println("\nthis is the end");
}
}
class DivideZero {
int x;
public static void main(String[] args) {
int y;
DivideZero c = new DivideZero();
try {
y = 3 / c.x;
} catch (ArithmeticException e) {
System.out.println("divide by zero error");
}
System.out.println("program ends ok!");
}
}
实例
编写一个类 ExceptionTest 在 main 方法中使用 try 、 catch 、 finally ,要求
- 在 try 块中,编写被零除的代码。
- 在 catch 块中,捕获被零除所产生的异常,并且打印异常信息
- 在 finally 块中,打印一条语句。
public class ExceptionTest {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.println("请输入除数:");
int op1 = input.nextInt();
System.out.println("请输入被除数:");
int op2 = input.nextInt();
int result = 0;
try {
result = op1 / op2;
} catch (ArithmeticException e) {
e.printStackTrace();
} finally {
System.out.println("异常处理");
}
System.out.println(result);
}
}
异常处理机制2:throws(抛出)
定义
- 如果一个方法 中的语句执行时 可能生成某种异常 但是并不能确定如何处理这种异常 则此方法应 显示地 声明抛出异常 表明该方法将不对这些异常进行处理而由该方法的 调用者 负责处理 。
- 在方法声明中用 throws 语句可以声明抛出异常的列表 throws 后面的异常类型可以是方法中产生的异常类型 也可以是它的父类 。
语法格式
void readFile(String file) throws FileNotFoundException {
……
//读文件的操作可能产生 FileNotFoundException 类型的异常
FileInputStream fis new FileInputStream(file);
……
}
举例
import java.io.*;
class ThrowsTest {
public static void main(String[] args) {
ThrowsTest t = new ThrowsTest();
try {
t.readFile();
} catch (IOException e) {
e.printStackTrace();
}
}
public void readFile() throws IOException {
FileInputStream in = new FileInputStream("atguigushk.txt");
int b;
b = in.read();
while (b != 1) {
System.out.print((char) b);
b = in.read();
}
in.close();
}
}
手动抛出异常
步骤
- 首先要生成异常类对象 然后通过 throw 语句实现抛出操作 提交给 Java 运行环境 。
IOException e=new IOException; throw e;
- 可以抛出的异常必须是 Throwable 或其子类的实例 。 下面的语句在编译时将会产生语法错误:
throw new String("want to throw");
用户自定义异常类
定义
- 一般地,用户自定义异常类都是 RuntimeException 的子类
- 自定义异常类通常需要编写几个 重载的构造 器 。
- 自定义异常需要提供 serialVersionUID
- 自定义的异常通过 throw 抛出 。
- 自定义异常最重要的是异常类的名字,当异常出现时,可以根据名字判断异常类型。
- 用户自定义异常类MyException ,用于描述数据取值范围错误信息。用户自己的异常类 必须继承 现有的异常类。
举例
class MyException extends Exception {
static final long serialVersionUID = 13465653435L;
private int idnumber;
public MyException(String message, int id) {
super(message);
this.idnumber = id;
}
public int getId() {
return idnumber;
}
}
class MyExpTest {
public void regist(int num) throws MyException {
if (num < 0) {
throw new MyException("人数为负值,不合理 ", 3);
} else {
System.out.println(" 登记人数 " + num);
}
}
public void manager() {
try {
regist(100);
} catch (MyException e) {
System.out.print(" 登记失败,出错种类 " + e.getId());
}
System.out.print("本次登记操作结束");
}
public static void main(String args[]) {
MyExpTest t = new MyExpTest();
t.manager();
}
}
实例
class ReturnExceptionDemo {
static void methodA() {
try {
System.out.println("进入方法 A ");
throw new RuntimeException("制造异常");
} finally {
System.out.println("用 A 方法的 finally");
}
}
static void methodB() {
try {
System.out.println("进入方法 B ");
return;
} finally {
System.out.println("调用 B 方法的 finally");
}
}
public
static void main(String[] args) {
try {
methodA();
} catch (Exception e) {
System.out.println(e.getMessage());
}
methodB();
}
}
实例:
编写应用程序 EcmDef.java 接收命令行的两个参数 要求不能输入负数 计算两数相除 。对数据类型不一致(NumberFormatException) 、 缺少命令行参数(ArrayIndexOutOfBoundsException) 、除0( ArithmeticException )及输入负数 (EcDef自定义的异常) 进行异常处理 。
import java.util.Scanner;
public class EcmDef {
public static void main(String[] args) {
String[] a = new String[2];
for (int i = 0; i < 2; i++) {
System.out.println("please input " + (i + 1) + " number:>");
Scanner A = new Scanner(System.in);
a[i] = A.next();
}
try {
int i = Integer.parseInt(a[0]);
int j = Integer.parseInt(a[1]);
int result = ecm(i, j);
System.out.println(result);
} catch (NumberFormatException e) {
System.out.println("数据类型不一致");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("缺少命令行参数");
} catch (ArithmeticException e) {
System.out.println("除0");
} catch (EcDef e) {
System.out.println(e.getMessage());
}
}
public static int ecm(int i, int j) throws EcDef {
if (i < 0 || j < 0) {
throw new EcDef("分子或分母为负数了!");
}
return i / j;
}
}
class EcDef extends Exception {
static final long serialVersionUID = -33875164229948L;
public EcDef() {
}
public EcDef(String msg) {
super(msg);
}
}
总结
多线程
程序、进程、线程
程序
- 是为完成特定任务、用某种语言编写 的一 组指令的集合 。即指 一段静态的代码 ,静态对象。
进程
- 是程序的一次执行过程,或是 正在运行的一个程序 。是 一个动态的过程 :有 它自身的产生、存在和消亡的 过程 。 ——生命周期
线程
- 进程可进一步细化为线程,是一个程序内部的一条执行路径。
- 若 一个进程同一时间 并行 执行多 个线程,就是支持多 线程的
- 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器 ( pc),线程切换的开销小
- 一个进程中的多个线程共享相同的内存单元 内存地址空间 , 它们从同一堆中分配 对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来 安全的隐患 。
多线程优点
- 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
- 提高计算机系统 CPU 的利用率
- 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
线程的创建和使用
线程的创建
通过java.lang.Thread类实现
Thread 类的特性
- 每个线程都是通过某个特定 Thread 对象的 run() 方法来完成操作的,经常把 run() 方法的主体称为 线程体
- 通过该 Thread 对象的 start() 方法 来启动这个线程,而非直接调用 run()
构造器
- Thread() 创建 新 的 Thread 对象
- Thread(String threadname) 创建线程并指定 线程实例名
- Thread( Runnable target) 指定 创建线程的目标对象,它 实现了 Runnable 接口中的 run 方法
- Thread(Runnable target, String name) 创建新的 Thread 对象
API中创建线程
1.继承Thread类
- 定义子类继承 Thread 类。
- 子类中重写 Thread 类中的 run 方法。
- 创建 Thread 子类对象,即创建了线程对象。
- 调用线程对象 start 方法:启动线程,调用 run 方法 。
举例
class MyThread extends Thread {
public MyThread() {
super();
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("子线程:" + i);
}
}
}
public class TestThread {
public static void main(String[] args) {
//创建线程
MyThread mt = new MyThread();
//启动线程,并调用run方法
mt.start();
}
}
注意
- 如果 自己手动调用 run() 方法,那么就只是普通方法,没有启动多线程模式。
- run 方法由 JVM 调用,什么时候调用,执行的过程控制都有操作系统的 CPU调度 决定
- 想 要启动多线程,必须调用 start 方法 。
- 一 个线程对象只能调用一次 start() 方法启动,如果重复调用了,则将抛出以上的异常“ IllegalThreadStateException” 。
2.实现 Runnable 接口
- 定义 子类 ,实现 Runnable 接口
- 子 类中重写 Runnable 接口中的 run 方法。
- 通过 Thread 类含参构造器创建线程 对象。
- 将 Runnable 接口的子类对象作为实际参数 传递 给 Thread 类 的构造器中 。
- 调用 Thread 类的 start 方法:开启线程 调用 Runnable 子类接口的 run 方法。
public class Thread extends Object implements Runnable
区别
- 继承 Thread :线程 代码存放 Thread 子类 run 方法中。
- 实现 Runnable :线程代码存在接口的子类的 run 方法。
实例: 创建两个分线程 ,让其中 一 个线程输出 1-100 之间的偶数,另 一个线程输出 1-100 之间的奇数。
// 定义子类继承Thread类
class MyThread1 extends Thread {
//子类中重写Thread类中的run方法。
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
}
}
// 定义子类继承Thread类
class MyThread2 extends Thread {
//子类中重写Thread类中的run方法。
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
System.out.println(i);
}
}
}
}
public class Solution {
// 创建Thread子类对象,即创建了线程对象。
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
t1.start();
MyThread2 t2 = new MyThread2();
t2.start();
}
}
Thread类有关方法
- void start()
- 启动线程,并执行对象的 run() 方法
- run()
- 线程在被调度时执行的 操作
- String getName ()
- 返回线程的名称
- void setName (String name)
- 设置该线程名称
- static Thread currentThread ()
- 返回 当前 线程 。在Thread 子类中就是 this ,通常用于主线程和 Runnable 实现类
- static void yield()
- 线程让步:暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程;若队列中没有同优先级的线程,忽略此方法
- join()
- 当某个程序执行流中调用其他线程的 join() 方法时 调用线程 将被阻塞,直到 join() 方法加入的 join 线程执行完为止;低优先级的线程也可以获得执行
- static void sleep(long millis)
- 指定时间 :毫秒,令当前活动线程在指定时间段内放弃对 CPU 控制 使其他线程有机会被执行 时间到后重排队;抛出 InterruptedException 异常
- stop()
- 强制线程 生命期 结束,不推荐使用
- boolean isAlive()
- 返回 boolean ,判断线程是否还活着
线程的生命周期
五种状态
- 新建: 当 一个 Thread 类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
- 就绪: 处于 新建 状态的线程被 start() 后,将进入线程队列等待 CPU 时间片,此时它已具备了运行 的 条件 ,只是没分配到 CPU 资源
- 运行: 当就绪的线程被调度 并 获得 CPU 资源 时 便进入运行状态, run() 方法定义了线程的操作和功能
- 阻塞: 在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
- 死亡: 线程完成了它的全部工作或线程被提前强制性 地 中止 或出现异常导致结束
线程的同步
实例:模拟火车站售票程序,开启三个窗口售票
class Window extends Thread {
static int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket--);
} else {
break;
}
}
}
}
public class TestWindow {
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
多线程出现了 安全 问题
原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有
执行完,另一个线程参与进来执行。导致共享数据的错误 。解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以
参与执行 。
Synchronizated
使用方法
1.同步代码块
synchronized(对象){
//需要被同步的代码;
}
2.synchronized 还可以放在方法声明中,表示 整个 方法为 同步方法 。
public synchronized void show (String name){
…
}
同步机制中的锁
同步锁机制:
- 在《 Thinking in Java 》 中 是这么说的:对于并发工作 你需要某种方式来防止两个任务访问相同的资源 其实就是共享资源竞争 。 防止这种冲突的方法就是当资源被一个任务使用时 在其上加锁。 第一个访问某项资源的任务必须锁定这项资源 使其他任务在其被解锁之前 就无法访问它了 而在其被解锁之时 另一个任务就可以锁定并使用它了 。
synchronized 的锁是什么
- 任意对象都可以作为同步锁 。 所有对象都自动含有单一的锁 监视器 。
- 同步方法的锁:静态方法( 类名.class) 、 非静态方法(this)
- 同步代码块:自己指定 很多时候也是指定为 this 或类名 class
注意:
- 必须确保使用同一个资源的 多个线程共用一把锁 这个非常重要 否则就无法保证共享资源的安全
- 一个线程类中的所有静态方法共用同一把锁(类名.class)所有非静态方法共用同一把锁 (this) 同步代码块( 指定需谨慎)