JAVA SE回顾拾遗
base
-
最好完全避免使用浮点数进行比较
float单精度浮点数:4字节,32位二进制数表示,有效位数为7 (有限长 离散 舍入误差 大约 接近但不等于)
double双精度浮点数:8字节,64位二进制数表示,有效位数为16 (浮点数默认为double类型)主要由于浮点型数据的精度问题
1.1用二进制表示为:1.000110…xxxx…(后面表示省略)
0.1 = …而double类型表示小数部分只有32位,当向后计算 32位后基数还不为0,那后面的部分只能舍弃,从这里可以看出float、double并不能准确表示每一位小数,对于有的小数只能无限趋向它。在计算机 中加减成除运算实际上最后都要在计算机中转换成二进制的加运算,由此,当计算机运行System.out.println(2.00-1.10);时会拿他们在计算机内存中的二进制表示计算,而1.10的二进制表示本身就不准确,所以会出0.8999999999999999的结果。但为什么System.out.println(2.00f-1.10f);得出的结果是0.9呢。因为float精度没有double精度那么大,小数部分0.1二进制表示被舍去的比较多。float a=0.1f; double b=1.0/10; System.out.println(a==b);//false
float c=12121212121212f; float d=c+1; System.out.println(c==d);//true
-
JAVA位运算
//与运算 System.out.println("A&B二进制为:"+Integer.toBinaryString(A&B));//A&B二进制为:100 //或运算 System.out.println("A|B二进制为:"+Integer.toBinaryString(A|B));//A|B二进制为:111 //异或运算 与1异或可以实现取反操作 System.out.println("A^B二进制为:"+Integer.toBinaryString(A^B));//A^B二进制为:11 //取反操作 System.out.println("~B二进制为:"+Integer.toBinaryString(~B));//~B二进制为:1011(高为全为1) /* 2*8 = 16 //2左移3位 << 左移 >> 右移 */ System.out.println(2<<3);//结果为16
-
JAVA字符串连接符
public static void main(String[] args) { int a=10; int b=20; //""字符串在前面 则将后面的也变为字符串格式进行连接 System.out.println(""+a+b);//1020 //""字符串在中间 则将前面和后面的也变为字符串格式进行连接 System.out.println(a+""+b+a);//102010 //""字符串在后面 则前面的数据继续运算 System.out.println(a+b+"");//30 }
-
JAVA默认值
8中基本数据类型的默认值:
①byte short int long 这四种基本数据类型数组默认值为0
②float double 这两种数组默认值是0.0
③char这种类型数组默认值为空格
④boolean类型数组默认值为false
扩展:java基本数据类型的默认值为0,引用数据类型的默认值为null;引用数据类型主要包括三种:类Class,接口Interface,数组;String 类型数据默认值为 null。
array
JAVA内存分区
-
堆:存放new的对象或者数组,可以被所有的线共享,不会存放别的对象引用 。如:可以存放类实例的属性和方法
-
栈:存放基本变量类型(包含各个基本类型的具体数据)
引用对象的变量(会存放这个引用在堆里面的具体地址)如:类的实例名(实际是个引用) -
**方法区:**可以被所有线程共享,包含了所有的class和static变量。方法区用于存储已被虚拟机加载的类信息、常量、静态变量、动态生成的类等数据。
-
Java空字符串与null的区别
1、类型
null表示的是一个对象的值,而不是一个字符串。例如声明一个对象的引用,String a=null。
“”表示的是一个空字符串,也就是说它的长度为0。例如声明一个字符串String s=“”。2、内存分配
String a=null;表示声明一个字符串对象的引用,但指向为null,也就是说还没有指向任何的内存空间。
String s=“”;表示声明一个字符串类型的引用,其值为“”空字符串,这个s引用指向的是空字符串的内存空间;在java中变量和引用变量是存在栈中(stack),而对象(new产生的)都是放在堆中(heap):
就如下:
String str =new String(“abc”);
ps:=左边的是存放在栈中(stack),=右边是存放在堆中(heap)。- 1 如果想调用一个方法,首先要有一个对象,但是null并不是一个对象,内存中都没有它的空间,所以 null是不能够调用String中的方法的,isEmpty和length和equals方法都不能够调用。
2 字符串对象与null的值不相等,且内存地址也不相等。
3 new String()创建一个字符串对象的默认值为“” (String类型成员变量的初始值为null)
- 1 如果想调用一个方法,首先要有一个对象,但是null并不是一个对象,内存中都没有它的空间,所以 null是不能够调用String中的方法的,isEmpty和length和equals方法都不能够调用。
java判断字符串是否都为空的方法
方法1
最多人使用的一个方法, 直观, 方便, 但效率很低:
if(s == null || s.equals(“”));
方法2
比较字符串长度, 效率高, 是我知道的最好一个方法:
if(s == null || s.length() == 0);
方法3
Java SE 6.0 才开始提供的方法, 效率和方法二几乎相等, 但出于兼容性考虑, 推荐使用方法二.
if(s == null || s.isEmpty());
方法4
这是一种比较直观,简便的方法,而且效率也非常的高,与方法二、三的效率差不多:
if (s == null || s == “”);
注意:s==null是有必要存在的。
如果 String 类型为null, 而去进行 equals(string) 或 length() 等操作会抛出java.lang.NullPointerException。
并且s==null 的顺序必须出现在前面,不然同样会抛出java.lang.NullPointerException。
数组基本操作
-
增强型for循环 array.for
int[] array={1,2,3,4,5,6}; //增强型for循环 array.for //array表示一个数组 i表示数组中元素 for (int i : array) { System.out.print(i+" ");//1 2 3 4 5 6 但是不会换行 }
-
打印数组
public static void printArray(int[] arrays){ for(int i=0;i<arrays.length;i++){ System.out.print(arrays[i]+" ");//1 2 3 4 5 6 但是不会换行 } }
-
数组翻转
public static int[] reverse(int[] arrays){ int[] result=new int[arrays.length]; //翻转操作 for(int i=0,j=result.length-1;i<arrays.length;i++,j--){ result[j]=arrays[i]; } return result; } int[] array={1,2,3,4,5,6}; //新建数组接收翻转后的数组 int[]a=reverse(array); //打印翻转后的数组 for (int i : a) { System.out.print(i+" ");//6 5 4 3 2 1 }
JAVA自带的数组类Arrays
-
打印数组 toString(Array[] array)
参数为数组
int[] a={1,2,23,20,5,6,1,10,4}; //打印数组元素 System.out.println(Arrays.toString(a));//[1, 2, 23, 20, 5, 6, 1, 10, 4]
-
排序方法 sort(Array[] array, int fromIndex, int toIndex)
参数1 数组, 参数2,3为数组索引(参数2,3可以没有)
int[] a={1,2,23,20,5,6,1,10,4}; //数组排序 升序 Arrays.sort(a); //打印排序后的数组 System.out.println(Arrays.toString(a));//[1, 1, 2, 4, 5, 6, 10, 20, 23]
-
二分法查找元素 binarySearch(Array[] array, int fromIndex, int toIndex, data key)
参数1数组,(参数2,3可以没有) 参数2,3为数组索引位置,用来对指定范围的数据进行排序, 参数4为要查找的值
注意参数1 的数组为排序后的数组
int[] a={1, 1, 2, 4, 5, 6, 10, 20, 23}; //利用二分发查找某个元素在数组中的索引位置 返回值为int类型的索引值 int num=Arrays.binarySearch(a,6); System.out.println(num);//输出5
-
数组填充 fill(Array[] array, int fromIndex, int toIndex, int val)
参数1为目标数组,参数2,3为索引位置(可以没有),参数4为填充值
int[] a={1, 1, 2, 4, 5, 6, 10, 20, 23}; //将a的第[2,4)下标的元素填充为0 Arrays.fill(a,2,4,0); System.out.println(Arrays.toString(a));//[1, 1, 0, 0, 5, 6, 10, 20, 23]
-
数组复制 copyof(Array[] array, int newLength)
参数1为数组待复制数组,参数2为复制长度,即复制后新数组长度
int[] a={1,2,23,20,5,6,1,10,4}; //完全复制a数组 int[] b=Arrays.copyOf(a,a.length); System.out.println(Arrays.toString(b));//[1, 2, 23, 20, 5, 6, 1, 10, 4] //只复制a数组前三个元素 若将复制后的数组赋值给原数组,可以实现缩容 int[] c=Arrays.copyOf(a,3); System.out.println(Arrays.toString(c));、//[1, 2, 23] //复制a数组元素并用0来扩充 若将复制后的数组幅值给原数组,可以实现扩容 int[] d=Arrays.copyOf(a,a.length+3);//扩充的位置补0 System.out.println(Arrays.toString(d));//[1, 2, 23, 20, 5, 6, 1, 10, 4, 0, 0, 0]
补充:System.arraycopy(Object src, int srcPos,Object dest, int destPos,int length)方法
src:源数组;
srcPos:源数组要复制的起始位置;
dest:目的数组;
destPos:目的数组放置的起始位置;
length:复制的长度。
int[] arr1={1,1,1,2,2,2}; int[] arr2={111,222,333,444,555,666,777,888}; System.arraycopy(arr1,1,arr2,1,4); System.out.println(Arrays.toString(arr2));//[111, 1, 1, 2, 2, 666, 777, 888]
-
稀疏数组
//稀疏数组 /* 行 列 值 0 行数 列数 非0值个数 --------------------- 1 行数 列数 值 2 3 ... */ int[][] array1=new int[11][11]; array1[1][2]=1; array1[2][3]=2; //输出原始数组 System.out.println("输出原始数组:"); //二维数组第一种打印方式 for (int[] ints : array1) { for (int anInt : ints) { System.out.print(anInt+"\t"); } System.out.println(); } //计算数组中非0值的个数 int sum=0; for (int i=0;i<11;i++){ for (int j=0;j<11;j++){ if(array1[i][j]!=0){ sum++; } } } System.out.println("有效值的个数为:"+sum); //建立稀疏数组 保存有效值 int[][] array2=new int[sum+1][3]; array2[0][0]=11; array2[0][1]=11; array2[0][2]=sum; //遍历二维数组 将非0值放在稀疏数组中 int count=0; //二维数组第二种打印方式 for (int i=0;i<array1.length;i++){ for (int j=0;j<array1[i].length;j++){ if(array1[i][j]!=0){ count++; //有一个有效值 稀疏数组列数就+1 array2[count][0]=i; array2[count][1]=j; array2[count][2]=array1[i][j]; } } } //输出稀疏数组 for(int i=0;i<array2.length;i++){ System.out.println(array2[i][0]+"\t" +array2[i][1]+"\t" +array2[i][2]+"\t"); } //稀疏数组还原 int[][] array3=new int[array2[0][0]][array2[0][1]]; int num=array2[0][2]; for(int i=1;i< array2.length;i++){ array3[array2[i][0]][array2[i][1]]=array2[i][2]; } //输出还原后的数组 System.out.println("输出还原后的数组:"); for (int i=0;i<array3.length;i++){ for (int j=0;j< array3[i].length;j++){ System.out.print(array3[i][j]+"\t"); } System.out.println(); }
scanner
-
创建 Scanner 对象的基本语法
public static void main(String[] args) { Scanner scanner = new Scanner(System.in); //输出内容 if (scanner.hasNextLine()){//判断是否还有输入 String str=scanner.nextLine();//接收输入 } scanner.close();//用完IO流以后 关闭 }
-
next() 与 nextLine() 区别
-
next():
1、一定要读取到有效字符后才可以结束输入,并且光标指向本行。
2、对输入有效字符之前遇到的空白,next() 方法会自动将其去掉。
3、只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符。(直接输入空格,不读取)
4、next() 不能得到带有空格的字符串。 -
nextLine():
1、以Enter为结束符,也就是说 nextLine()方法返回的是输入回车之前的所有字符。
2、可以获得空白。3、读取输入后,nextLine()将光标定位在下一行。
-
-
hasNext() 与 hasNextLine() 区别
通过 Scanner 类的 next() 与 nextLine() 方法获取输入的字符串,在读取前我们一般需要使用 hasNext 与 hasNextLine 判断是否还有输入的数据。
-
hasNext():
1、输出为布尔值。
2、判断输入的缓存中是否有效字符,遇到空格结束。
3、如果只输入空格,不会匹配,返回false。 -
hasNextLine():
1、以Enter为结束符,判断此行有没有输入,空白输入也会返回true。
-
struct
-
Lable
//打印101-150之间的质数 int count=0; outer:for (int i=101;i<=150;i++){ for (int j=2;j<i/2;j++){ if (i%j==0){ continue outer;//从内部的循环 跳转到标签名的循环 } } System.out.print(i+" "); }
method
-
Java中的方法是值传递
-
方法的重载规则:
- 方法名必须相同
- 参数列表必须不同(参数个数、参数类型、参数顺序不同)
- 方法的返回类型可以相同也可以不同
- 仅仅是返回值类型不同不足以成为方法的重载
-
参数传递
对于基本类型(以及String)来说,形式参数的操作【不会】影响实际参数。
对于引用类型(除了String)来说,形式参数的操作【会】影响实际参数。
-
方法重载和重写
主要有以下区别:1、首先是含义不同
1)方法重载是在同一个类中,声明多个同名方法,通过参数列表来区分不同的方法,与参数列表的数量、类型和顺序有关,与修饰符和返回值类型以及抛出异常类型无关
2)方法重写(方法覆盖)的前提是发生在具有继承关系的两个类之间,方法重写有以下规则:
a.参数列表必须保持一致
b.返回值类型必须保持一致
c.方法名必须保持一致
d.重写方法的访问权限范围必须大于等于父类方法
e.重写方法的抛出异常类型范围不能大于父类方法
2、方法的重载和重写的作用不同
重载:在一个类中为一种行为提供多种实现方式并提高可读性
重写:父类方法无法满足子类的要求,子类通过方法重写满足需求
opp
-
面向对象
public class Opp { //面向对象编程(Object-Oriented Programming,OPP) //面向对象的本质是: 以类的方式组织代码,以对象的组织(封装)数据。 public static void main(String[] args) { System.out.println("面向对象编程(Object-Oriented Programming,OPP)"); System.out.println("面向对象的本质是: 以类的方式组织代码,以对象的组织(封装)数据"); System.out.println("三大特性: 封装 继承 多态"); } }
-
静态方法和非静态方法区别
-
调用方法、调用对象、引用变量不同
-
静态方法(static 修饰):又叫类方法,属于类的,不属于对象
(1)可以用类名直接调用 “类名.方法名” ;也可以用对象调用 “对象名.方法名”;
(2)可以调用静态方法,不能调用非静态方法;
(3)可以引用类变量(static修饰的变量),不可以引用成员变量(无static修饰);
(4)在静态方法中,不能使用super和this关键字;
(5)静态static变量/方法 在类加载的过程中被初始化,在内存中只存在一份,所以可以把它当作是全局变量/方法。
-
非静态方法(无static 修饰):又称为实例方法,成员方法。属于对象的,不属于类的
(1)先将类实例化,利用实例对象调用非静态类方法 “对象名.方法名”;
(2)可以调用普通方法,可以调用静态方法;
(3)可以引用类变量和成员变量;
(4)可以使用super和this关键字。
-
-
生命周期不同
- 静态方法的生命周期跟相应的类一样长,静态方法和静态变量会随着类的定义而被分配和装载入内存中。一直到线程结束,静态属性和方法才会被销毁。(也就是静态方法属于类)
- 非静态方法的生命周期和类的实例化对象一样长,只有当类实例化了一个对象,非静态方法才会被创建,而当这个对象被销毁时,非静态方法也马上被销毁。(也就是非静态方法属于对象)
-
-
构造方法
- 函数名与类名相同
- 不用定义返回值类型(不同于void类型返回值,void时没有具体返回值类型,构造方法是连类型都没有)
- 不可以写return语句(返回值类型都没有,故不需要return语句)
- 一般方法不能调用构造方法,只有构造方法才能调用构造方法
- 一个对象建立后,构造函数仅只运行一次,如果想给对象的值再赋新的值,就要使用set和get方法,此时是当做一般函数使用
- 类中未定义构方法时,系统默认给类添加一个无参构造;当在该类中自定义了构造函数,默认构造函数就没有了,如果仍要无参构造,则需要在类中手动添加
- 构造函数也是函数的一种,同样具备函数的重载(Overloding)特性
- 子类构造函数中隐藏了super( ),用来调用父类的构造函数;即使显示写出调用父类的无参构造,也必须要在子类构造器的第一行写出;如果父类里面没有定义参数为空的构造函数,那么必须在子类的构造函数的第一行显示的调用super(参数)语句调用父类当中其它的构造函数
- 如果父类中没有无参构造,则子类在定义构造方法时会报错,因为子类构造中默认的第一句super()会调用父类无参构造,但父类没有无参构造;解决方法:(1)在父类中手动添加一个无参构造;(2)在子类构造中调用父类有参数构造方法super(参数);(3)在子类构造方法中调用本类中其他构造方法,来挤掉super()调用父类中无参数构造方法,如this()
-
super()
-
super可以理解为是指向自己超(父)类对象的一个指针,而这个超类指的是离自己最近的一个父类。
-
super的作用主要在下面三种情况下:
(1)调用父类被子类重写的方法;
(2)调用父类被子类重定义的字段(被隐藏的成员变量);
(3)调用父类的构造方法;
其他情况,由于子类自动继承了父类相应属性方法,关键字super可以不显示写出来。
-
-
重写(Override)与重载(Overload)
-
重写(Override)
重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
- 方法的重写规则
- 参数列表与被重写方法的参数列表必须完全相同。
- 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
- 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
- 父类的成员方法只能被它的子类重写。
- 声明为 final 的方法不能被重写。
- 声明为 static 的方法不能被重写,但是能够被再次声明。
- 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
- 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
- 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
- 构造方法不能被重写。
- 如果不能继承一个类,则不能重写该类的方法。
class Animal{ public void move(){ System.out.println("动物可以移动"); } } class Dog extends Animal{ public void move(){ System.out.println("狗可以跑和走"); } } public class TestDog{ public static void main(String args[]){ Animal a = new Animal(); // Animal 对象 //父类引用指向了子类对象 Animal b = new Dog(); // Dog 对象 a.move();// 执行 Animal 类的方法 b.move();//执行 Dog 类的方法 } }
运行结果: 动物可以移动 狗可以跑和走
在上面的例子中可以看到,尽管 b 属于 Animal 类型,但是它运行的是 Dog 类的 move方法。
这是由于在编译阶段,只是检查参数的引用类型。
然而在运行时,Java 虚拟机(JVM)指定对象的类型并且运行该对象的方法。
因此在上面的例子中,之所以能编译成功,是因为 Animal 类中存在 move 方法,然而运行时,运行的是特定对象的方法。
- 方法的重写规则
-
重载(Overload)
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
最常用的地方就是构造器的重载。
- 重载规则
- 被重载的方法必须改变参数列表(参数个数或类型不一样);
- 被重载的方法可以改变返回类型;
- 被重载的方法可以改变访问修饰符;
- 被重载的方法可以声明新的或更广的检查异常;
- 方法能够在同一个类中或者在一个子类中被重载。
- 无法以返回值类型作为重载函数的区分标准。
- 重载规则
-
重写与重载之间的区别
区别点 重载方法 重写方法 参数列表 必须修改 一定不能修改 返回类型 可以修改 一定不能修改 异常 可以修改 可以减少或删除,一定不能抛出新的或者更广的异常 访问 可以修改 一定不能做更严格的限制(可以降低限制) -
总结
方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现(运行时的多态性),重载可以理解成多态的具体表现形式(编译时的多态性)。
- 方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。
- 方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。
- 方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
-
-
多态
多态是同一个行为具有多个不同表现形式或形态的能力。
-
多态是方法的多态,属性没有多态
-
多态存在的三个必要条件:
继承关系、方法需要重写、父类引用指向子类对象:Parent p = new Child();
-
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。
多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。
public class Test { public static void main(String[] args) { show(new Cat()); // 以 Cat 对象调用 show 方法 show(new Dog()); // 以 Dog 对象调用 show 方法 Animal a = new Cat(); // 向上转型 a.eat(); // 调用的是 Cat 的 eat Cat c = (Cat)a; // 向下转型 c.work(); // 调用的是 Cat 的 work } public static void show(Animal a) { a.eat(); // 类型判断 if (a instanceof Cat) { // 猫做的事情 Cat c = (Cat)a; c.work(); } else if (a instanceof Dog) { // 狗做的事情 Dog c = (Dog)a; c.work(); } } } abstract class Animal { abstract void eat(); } class Cat extends Animal { public void eat() { System.out.println("吃鱼"); } public void work() { System.out.println("抓老鼠"); } } class Dog extends Animal { public void eat() { System.out.println("吃骨头"); } public void work() { System.out.println("看家"); } }
执行以上程序后,输出结果为:
吃鱼 抓老鼠 吃骨头 看家 吃鱼 抓老鼠
- 多态的实现方式:重写;接口;抽象类和抽象方法。
-
-
抽象类
在 Java 语言中使用 abstract class 来定义抽象类。抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
Abstract 关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。
抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。
-
抽象类特点:
- 抽象类不能被实例化,如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
- 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
- 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
- 抽象类的子类必须给出抽象类中的所有抽象方法的具体实现,如果子类没有重写完父类中的所有抽象方法,则该子类必须为抽象类。
- 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。
-
思考:
- abstract final class Mammal{ } 能编译通过吗, why?
不能通过。如果抽象类前面加final意味着该类不能继承,则抽象类的抽象方法永远不能实现,所以不能加final。 - Mammal抽象类中move抽象方法的访问权限可以为private吗,即“private abstract void move();”, why?
不可以,被private修饰的类只能在本类起作用,则抽象方法不能被实现。 - Mammal抽象类中move抽象方法可以由static修饰吗,,即“public static abstract void move();” why?
不可以,如果加static修饰,则可以通过类名直接调用方法,可是抽象方法不能直接被调用,所以是无意义的。
- abstract final class Mammal{ } 能编译通过吗, why?
-
-
接口
接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。
-
接口定义
//抽象思维 //接口 interface 接口都需要由实现类 //接口中没有构造方法 public interface UseService { //接口中定义的属性 是个常量 public static final int age=12; //接口中的所有定义其实都是抽象的 public abstract void delete(String name); void add(String name); } public interface TimeService { void time(); }
-
接口特性
- 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。
- 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
- 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
- 一个接口能继承(extends)另一个接口,和类之间的继承方式比较相似。接口的继承使用extends关键字,子接口继承父接口的方法。
- 类的多继承是不合法,但接口允许多继承,在接口的多继承中extends关键字只需要使用一次,在其后跟着继承接口。
-
接口的实现
当类实现接口的时候,类要实现接口中所有的方法。否则,类必须声明为抽象的类。
类使用implements关键字实现接口。在类声明中,Implements关键字放在class声明后面。
//类继承类(或者接口继承接口)关键字 extends //类继承接口关键字 implements //用来实现接口的类,就必须重写接口中的所有方法,除非它是个抽象类 public class UseServiceImpl implements UseService,TimeService{ //抽象类是约束,但只能单继承 接口也是约束,但是可以多继承 @Override public void delete(String name) { } @Override public void add(String name) { } @Override public void time() { } }
接口实现注意点:
- 一个类可以同时实现多个接口。
- 一个类只能继承一个类,但是能实现多个接口。
- 一个接口能继承另一个接口,这和类之间的继承比较相似。
-
接口与类的区别
- 接口不能用于实例化对象,但是类可以。(抽象类除外)
- 接口没有构造方法。
- 接口中所有的方法必须是抽象方法,Java 8 之后 接口中可以使用 default 关键字修饰的非抽象方法。
- 接口不能包含成员变量,除了 static 和 final 变量。
- 接口不是被类继承了,而是要被类实现。
- 接口支持多继承,一个接口可以extends多个接口。
-
抽象类与接口的区别
- 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不可以有实现体。
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
- 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
- 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
-
-
内部类
-
成员内部类
-
成员内部类是最普通的内部类,它的定义为位于另一个类的内部。
public class Outer { private int id=10; //方法里定义类 局部内部类 public void method(){ class inner{ public void in01(){ System.out.println("局部内部类方法"); } } } public void out(){ System.out.println("这是一个外部类方法"); } //外部类中定义的成员内部类 class Inner{ public void in(){ //加static 可使其变为静态内部类 // (静态内部类 只要外部类加载了就可以访问) // 所以静态内部类无法访问外部类的非静态成员 System.out.println("这是一个内部类的方法"); } //通过内部类获取外部类的私有属性 public void getId(){ System.out.println(id); } } } //一个java类中只能有一个public class,但是可以有多个class类 class A{ public static void main(String[] args) { } }
-
成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。
-
在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问
-
成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。创建成员内部类对象的一般方式如下:
public class Test { public static void main(String[] args) { //第一种方式: Outter outter = new Outter(); Outter.Inner inner = outter.new Inner(); //必须通过Outter对象来创建 //第二种方式: Outter.Inner inner1 = outter.getInnerInstance(); } } class Outter { private Inner inner = null; public Outter() { } //用来创建成员内部类对象 public Inner getInnerInstance() { if(inner == null) inner = new Inner(); return inner; } //成员内部类 class Inner { public Inner() { } } }
-
-
局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
注意,局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。
-
静态内部类
静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。
-
匿名内部类
public class OuterClass { public InnerClass getInnerClass(final int num,String str2){ return new InnerClass(){ //new了一个匿名内部类 int number = num + 3; public int getNumber(){ return number; } }; /* 注意:分号不能省 */ } public static void main(String[] args) { OuterClass out = new OuterClass(); InnerClass inner = out.getInnerClass(2, "chenssy"); System.out.println(inner.getNumber()); } } interface InnerClass { int getNumber(); } ---------------- Output: 5
- 匿名内部类是没有访问修饰符和static修饰的。
- new 匿名内部类,这个类首先是要存在的。如果我们将那个InnerClass接口注释掉,就会出现编译出错。
- 注意getInnerClass()方法的形参,第一个形参是用final修饰的,而第二个却没有。同时我们也发现第二个形参在匿名内部类中没有使用过,所以当所在方法的形参需要被匿名内部类使用,那么这个形参就必须为final。
- 匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。
-
exception
-
程序错误分为三种:1.编译错误;2.运行时错误;3.逻辑错误。
(1)编译错误是因为程序没有遵循语法规则,编译程序能够自己发现并且提示我们错误的原因和位置,这个也是大家在刚接触编程语言最常遇到的问题。
(2)运行时错误是因为程序在执行时,运行环境发现了不能执行的操作。
(3)逻辑错误是因为程序没有按照预期的逻辑顺序执行。异常也就是指程序运行时发生错误,而异常处理就是对这些错误进行处理和控制。 -
异常的结构
在 Java 中,所有的异常都有一个共同的祖先 Throwable(可抛出)。Throwable 指定代码中可用异常传播机制通过 Java 应用程序传输的任何问题的共性。
-
Throwable: 有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含大量子类。异常和错误的区别是:异常能被程序本身可以处理,错误是无法处理。
Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。
Exception(异常):是程序本身可以处理的异常。Exception 类有一个重要的子类 RuntimeException。RuntimeException 类及其子类表示“JVM 常用操作”引发的错误。例如,若试图使用空值对象引用、除数为零或数组越界,则分别引发运行时异常(NullPointerException、ArithmeticException)和ArrayIndexOutOfBoundException。
-
异常捕和抛出
基本语法
public class Test { public static void main(String[] args) { new Test().test(1,0); /* try{ //try监控区域 if(b==0){ throw new ArithmeticException();//主动抛出异常 } System.out.println(a/b); }catch (ArithmeticException e){ //catch捕获异常 System.out.println("程序异常,变量b不能为0"); }catch (Throwable e){ //catch捕获异常 Throwable>Error、Exception>其他 System.out.println("程序异常,变量b不能为0"); }finally {//处理善后工作 System.out.println("finally"); } //finally可以不用 假设IO,资源需要关闭 需要finally*/ } public void test(int a,int b){ if(b==0){ throw new ArithmeticException();//主动抛出异常 放在方法中 } System.out.println(a/b); } }
自定义异常
//自定义的异常类 继承 Exception public class MyException extends Exception{ //传递数字>10; private int detail; public MyException(int a) { this.detail = a; } //toString打印异常信息 @Override public String toString() { return "MyException{" + "detail=" + detail + '}'; } }
public class Test { //可能会存在异常的方法 static void test(int a)throws MyException{ System.out.println("传递的参数为:"+a); if(a>10){ throw new MyException(a);//抛出 } System.out.println("OK"); } public static void main(String[] args) { try { test(11); } catch (MyException e) { System.out.println("MyExcepyion=>"+e); } } }