static
static表示静态,可以修饰成员方法、成员变量。被static修饰的成员方法/成员变量被称为静态变量/静态方法。静态变量/静态方法可以直接通过类名调用。
静态变量
静态变量被该类所有对象共享,只需赋值一次,该类的全部对象的静态变量都被赋值。静态变量可以 通过类名或对象名调用。
静态变量是随着类的加载而加载的,它优先于对象出现。静态变量是类的属性,而不是对象的属性。
静态方法
静态方法通常用在测试类和工具类中。
工具类
工具类是指“帮助我们做一些事情,但是不描述任何事物的类”。
定义工具类时,要求:
- 类名见名知意
- 私有化构造方法,使在外界不能创建工具类的对象,因为工具类不描述任何事物,创建它的对象是没有意义的。
- 工具类的成员方法都定义为静态的,方便调用。
注意事项
- 静态方法只能访问静态变量和静态方法。
- 非静态方法可以访问静态变量或者静态方法,也可以访问非静态的成员变量和非静态的成员方法。
- 静态方法中是没有this关键字的。
Arraylist
继承
父类中哪些内容能够被子类继承?
- 子类不能继承父类的构造方法
- 子内能继承父类的成员变量,但子类虽然继承了父类的私有成员变量,却不能调用。
- 只有父类中的虚方法才能被子类继承,虚方法是“非private、非static、非final”修饰的方法,每个子类继承父类的虚方法表,并向其中添加自己的虚方法。
super关键字
类似于”this指定本类中的成员变量“,super指定父类中的成员变量。
this可以理解为一个指向当前方法调用者的指针。
函数重写
重写的本质是子类将从父类继承到的虚方法表中的方法进行覆盖的过程。因为“私有的”、“静态的“、”最终的”方法不会被添加到虚方法表中,因此不能被子类继承,也就不能被重写。
子类构造函数
子类的构造函数第一句默认为:super();
这句必须处在构造函数的第一句,不写会默认添加。
功能是在创建子类对象前,先调用父类的空参构造
如果想调用父类的有参构造,必须手动写:super(参数1,参数2)
多态
多态的好处:
使用父类型作为参数,可以接收所有的子类对象,
体现多态的扩展性和便利性。
多态中调用成员的特点
当Animal类有子类Dog时,写如下代码
Animal a = new Dog();
a.name;//调用成员变量
a.show();//调用成员方法
若父类与子类都有上述的变量与方法,则不会报错。若只有子类有上述变量与方法,父类没有,则会报错。
运行时,a.name返回父类成员变量;a.show()返回子类成员方法。
多态的缺点是父类不能调用子类独有的方法,比如在上面的例子中,假设Dog类中有个watchHome()方法是父类Animal没有的,那么a.watchHome()是不能调用的。
解决方法是将对象a强制转换为(真正的)Dog类后再调用watchHome()方法。
Dog d = (Dog) a;
d.watchHome();
对象a是由Dog()类new出来的,所以可以强制转换为(真正的)Dog类型。但不能强制转换为其他类型,比如:
Cat c = (Cat) a;//代码报错
为了避免错误的强制转换,我们在转换前先确定对象的类型,然后再转换:
//写法一
if(a instanceof Dog)//判断a是否是Dog类型
{
Dog d = (Dog) a;//将a强转成Dog类型
d.watchHome();//调用Dog类型的独有方法
}
else if(a instanceof Cat)
{
Cat c = (Cat) a;
c.catchMouse();
}
else
{
sout("没有这个类型,无法强制转换");
}
//写法二
if(a instanceof Dog d)//先判断a是否是Dog类型,如果是,则强转成Dog类型,转换后变量名为d。如果不是,则不强制转换,返回false
{
d.watchHome();
}
else if(a instanceof Cat c)
{
c.catchMouse();
}
else
{
sout("没有这个类型,无法强制转换");
}
final关键字
final意思是最终的、不能被改变的。
final修饰方法,说明该方法是最终方法,不能被重写。
final修饰类,表示该类是最终类,不能被继承。
final修饰变量,则该变量称作常量,只能被赋值一次
权限修饰符
代码块
构造代码块
写在成员位置,在创建本类对象时先执行构造代码块再执行构造方法。作用是将构造方法中重复的代码提取出来。
public class Student{
public static void main()
{
int age;
String name;
{//构造代码块
System.out.println("开始创建对象,先调用构造代码块");
}
}
public Student()
{
System.out.println("调用构造代码块后,再调用构造方法");
}
}
静态代码块
通过static关键字修饰的构造代码块,随着类的加载而加载,一个类只调用一次。
public class Student{
public static void main()
{
int age;
String name;
static {//静态 代码块
System.out.println("开始创建对象,先调用构造代码块");
}
}
public Student()
{
System.out.println("调用构造代码块后,再调用构造方法");
}
}
抽象类
当两个子类Student和Teacher同时继承父类Person的work()方法时,需要各自重写适用于自身的work()方法,此时父类的work()方法不能确定具体的方法体,可以写作抽象方法待子类重写。子类必须重写父类的抽象方法,否则报错。含有抽象方法的类必须声明为抽象类。
public abstract class Person{
public abstract void work();
}
- 抽象类不能实例化对象。
- 抽象类不一定有抽象方法,但有抽象方法的类一定是抽象类。
- 抽象类可以有构造方法,这是为了当创建子类对象时,让子类调用以给属性进行赋值的。
- 抽象类的子类要么重写抽象类的所有抽象方法,要么本身也是个抽象类。
接口
接口是行为的抽象,想要某些类获得某个具体的功能时,只要给这些类实现对应的接口即可。(接口像一个所有方法都是抽象方法的抽象类)
- 接口用关键字interface来定义:public interface 接口名{}
- 接口不能实例化
- 接口和类是实现的关系,通过implements关键字表示:public class 类名 implements 接口名{}
- 一个类可以实现多个接口:public class 类名 implements 接口名1, 接口名2{}
- 实现类还可以在继承一个类的同时实现多个接口:public class 类名 extends 父类 implements 接口名1, 接口名2{}
//游泳接口
public interface Swim{
public abstract void swim();
}
接口中成员的特点
- 成员变量
- 只能是常量
- 默认修饰符:public static final
- 接口没有构造方法
- 成员方法
- 只能是抽象方法(jdk7后更新为可以写方法体)
- 默认修饰符:public abstract
- 多个接口中有重名的方法,实现时只要重写一次就可以了。
接口可以继承一个或多个接口。如果实现类实现了最下面的子接口,那么需要重写所有的抽象方法
接口的应用
**接口的多态:**如果某个方法的参数是接口,那么可以通过传入这个接口的实现类的对象来调用这个方法。
(接口类型 j = new 实现类对象(); )
实现类可以看作是接口的子类
接口中的默认方法
jdk8后允许在接口中定义默认方法,需要使用关键字default修饰。这样可以解决在接口中增加方法导致实现类报错的问题。
接口中默认方法的定义格式:
格式: public default 返回值类型 方法名(参数列表){ }
public default void show(){}
注意:
- 默认方法不是抽象方法,所以不强制被重写。如果要重写,重写时不要写default关键字。
- 如果某个类实现了多个接口,多个接口中存在相同名字的默认方法,那么子类必须对该方法进行重写。
接口中的静态方法
静态方法不能重写,只能通过接口名调用。
私有方法
第一种写法是普通的私有方法,为默认方法服务。
第一种写法是静态的私有方法,为静态方法服务。
public interface Inter{
public default void show1()//默认方法1
{
System.out.println("show1方法开始执行了");
show3();
}
public default void show2()//默认方法2
{
System.out.println("show2方法开始执行了");
show3();
}
private void show3()//普通的私有方法,为默认方法1服务
{
System.out.println("此处表示提取出的重复代码");
}
public static void show4()//静态方法1
{
System.out.println("show1方法开始执行了");
show3();
}
public static void show5()//静态方法2
{
System.out.println("show2方法开始执行了");
show3();
}
private static void show6()//静态的私有方法,为静态方法服务
{
System.out.println("此处表示提取出的重复代码");
}
}
接口做参数
如果某个方法的参数是一个接口,那么可以传入任意一个这个接口的实现类对象。
内部类
类的五大成员:
属性、方法、构造方法、代码块、内部类。
public class Car{//外部类
String carName;
int carAge;
String carColor;
public void show()
{
System.out.println(carName);
Engine e = new Engine();
System.out.println(e.engineName);//外部类要访问内部类的成员,必须创建对象。
}
public class Engine{//内部类
Sting engineName;
int engineAge;
public void show()
{
System.out.println(engineName);//内部类可以直接访问外部类的成员,包括私有成员
System.out.println(carName);
}
}
}
内部类可以直接访问外部类的成员,包括私有成员
外部类要访问内部类的成员,必须创建对象。
内部类的分类
成员内部类
-
写在成员位置,属于外部类的成员。
-
成员内部类可以被权限修饰符修饰。被private修饰的成员内部类只能在外部类内实例化对象。
-
获取成员内部类的对象:
-
方法一:在外部类中编写方法,对外提供内部类的对象。
class Outer{ Stirng name; private class Inner{//成员内部类被private修饰,不能再外部类外实例化内部类,只能通过外部类的方法对外提供内部类对象 } public Inner getInstance(){ return new Inner(); } } public class test{ Outer o = new Outer(); //因为成员内部类是私有的,外部类外并不知道内部类的存在,所以不能用内部类类型来接收内部类对象,可以使用内部类的父类接收。 Object inner = o.getInstance(); //或者不接收对象,直接使用方法获取到的对象 System.out.println(o.getInstance()); }
声明一个 返回值是内部类类型 的方法。
-
方法二:直接创建格式:外部类名.内部类名 对象名 = 外部类对象.内部类对象;
class Outer{ Stirng name; class Inner{ } } public class test{ Outer.Inner oi = new Outer().new Inner(); //new Outer()相当于创建了一个Outer对象,接着通过这个对象创建成员内部类、 //类比调用成员变量的过程: //Outer() out = new Outer(); //String s = out.name 这句相当于 String s = new Outer().name //可以推出: //Outer() out = new Outer(); //Outer.Inner oi = out.new Inner() 这句相当于 Outer.Inner oi = new Outer().new Inner() }
-
-
成员内部类的就近原则
class Outer{ private int a = 10; class Inner{ private int a = 20; public void show(){ int a = 30; System.out.println(a);//打印30 System.out.println(this.a);//打印20 System.out.println(Outer.this.a);//打印10 } } }
成员内部类的内存中自动保存一个指向外部类的指针Outer.this,通过Outer.this这个指针可以调用外部类的成员变量。
静态内部类
静态内部类是特殊的成员内部类。
静态内部类只能访问外部类中的静态变量和静态方法,如果想要访问非静态的,需要创建对象。
创建静态内部类对象的格式:
//外部类名.内部类名 对象名 = new 外部类名.内部类名();
Outer.Inner oi = new Outer.Inner();
调用静态内部类中非静态方法的格式:先创建静态内部类对象,用对象调用
调用静态内部类中静态方法的格式:外部类名.内部类名.方法名();
class Outer{
staitc class Inner{
public void show1(){//非静态方法
}
static public void show2(){//静态方法
}
}
}
局部内部类
- 将内部类定义在方法里面就叫做局部内部类,类似于方法里面的局部变量。
- 外界是无法直接使用局部内部类的,需要再方法内部创建对象并使用。
- 该类可以直接访问外部类的成员,也可以访问方法内的局部变量。
匿名内部类
匿名内部类本质上是隐藏了名字的内部类,定义一个匿名内部类实际上是完成了:
- 实现接口或继承父类
- 重写方法
- 创建对象
这三个操作。
//格式:
new 类名或接口名() {
//重写方法;
};
下面解释这个格式
//一个一般的实现类的定义是这样的
public class Student implements Swim{//类的开始
@Override//重写接口
public void swim(){
System.out.println("重写的游泳方法");
}
}//类的结束
/*
括号内的代码是类的内容,public class Student implements Swim 这句是这个类的名字,如果删去这句,那么这个类就是一个没有名字的类,因为这个类要实现Swim接口,所以在开头写上接口名Swim
*/
Swim{//类的开始
@Override//重写接口
public void swim(){
System.out.println("重写的游泳方法");
}
}//类的结束
/*
现在的代码表示了一个“没有名字的类”实现了Swim接口,接下来创建这个类的对象.
一般创建类的对象的格式是:new 类名();仿照这个格式:
*/
new Swim(){//类的开始
@Override//重写接口
public void swim(){
System.out.println("重写的游泳方法");
}
};//注意这个f
Lambda表达式
//不sLambda表达式书写匿名类
Arrays.sort(arr, new Comparator<Integer>(){
@override
public int compare(Integer o1, Integer o2){
return o1 - o2;
}
});
//使用lambda表达式
Arrays.sort(arr, (Integer o1, Integer o2) -> {
return o1 - o2;
}
);//注意括号
//Lambda表达式的标准格式
() -> {
}
// () 对应方法的形参
// -> 固定格式
// {} 对应方法的方法体
Lambda是用来简化匿名类书写的,但只能简化函数式接口的匿名内部类的写法。有且仅有一个抽象方法的接口叫做函数式接口。
Lambda是一个匿名函数,可以将Lambda表达式理解成一段可传递的代码,它可以写出更简洁更灵活更紧凑的代码。
更省略的Lambda表达式
省略规则:
- 参数类型可以省略不写
- 如果只有一个参数,那么参数类型可以省略,同时()也可以省略。
- 如果Lambda表达式的方法体只有一行,那么大括号、分号、return可以省略不写(如果要省略必须同时省略)
//使用lambda表达式
Arrays.sort(arr, (Integer o1, Integer o2) -> {
return o1 - o2;
}
);//注意括号
//更省略的Lambda表达式
Arrays.sort(arr, (o1, o2) -> o1 - o2);
异常
异常体系最上层的父类是Exception,异常分两类:编译时异常和运行时异常
编译时异常是没有继承RuntimeException的异常,直接继承于Exception,编译阶段就会报错。
运行时异常是RuntimeException本身和其子类,编译阶段没有错误提示,运行时才会出现。
异常的三种处理办法
JVM虚拟机自动处理
一般情况下JVM虚拟机自动处理异常,处理办法是将错误信息打印到控制台,并在异常处中断代码的执行。
程序员自己处理(try…catch)
程序员手动处理异常,这种方法可以让程序在处理完异常后继续往下执行,不会停止。
int[] arr = {1, 2, 3, 4, 5};
try{
System.out.println(arr[10]);
//此处出现了异常,程序就会在这里创建一个ArrayIndexOutOfBoundsException对象,拿着这个对象在catch的小括号中对比,看括号中的变量是否可以接收这个异常对象,如果能被接收,就表示改异常被捕获(抓住),那么执行catch里面对应的代码,当catch里面的所有代码执行完毕,继续执行try...catch体系下面的其他代码。
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("索引越界了");
}
System.out.println("继续执行后续代码");
-
如果try中没有遇到问题,java会把try中所有的代码全部执行完毕,不会执行catch中的代码。
-
如果try中可能遇到多个问题,需要写多个catch与之对应。注意:
- 捕捉到一个异常后,执行完对应的catch代码后,会跳过try内剩余的代码,直接执行try语句外面的代码。
- 如果这些异常之间存在父子关系,那么父类一定要写在下面。因为程序自上而下匹配catch,会在父类就完成匹配。
public class Main { public static void main(String[] args) { int[] arr = {1, 2, 3, 4, 5}; try { System.out.println(arr[10]); System.out.println(2/0); String s = null; System.out.println(s.equals("abc")); } catch (ArrayIndexOutOfBoundsException e) { //一旦捕捉到异常,执行完对应catch后,会跳过try中剩余的代码,直接执行try外的代码。 System.out.println("数组越界了"); e.printStackTrace(); //把异常的错误信息用红色字体输出在控制台。仅打印信息,不停止程序。 }catch (ArithmeticException | NullPointerException e){ System.out.println("除数不能为0 或 空指针异常"); //jdk7以后可以在catch中同时捕获多个异常,中间用一个竖线隔开。 }catch (Exception e){ System.out.println("Exception!"); //父类的异常必须写在下面 } System.out.println("程序结尾"); } }
-
如果try中遇到的问题没有与之匹配的catch,那么将异常交给虚拟机默认处理(打印错误信息并停止程序)。
-
如果try中遇到了问题,那么try中剩余的代码就不会执行了,直接跳转到对应的catch中,执行catch中的代码,然后跳出try语句。如果没有与之匹配的catch,那么将异常交给虚拟机默认处理。
抛出处理
throws
throws写在方法定义处,表示声明一个异常。告诉调用者,使用本方法可能会出现哪些异常。
public void 方法() throws 异常类名1, 异常类名1...{
...
}
编译时异常必须要写,运行时异常可以不写。
throw
throw写在方法内,用来手动抛出异常,交给调用者,并结束方法。方法中下面的代码就不再执行了。
public void 方法(){
throw new NullPointerException();
}
throws和throw的例子
public class Main {
public static void main(String[] args){
int[] arr = null;
int max = 0;
try {
max = getMax(arr);
} catch (NullPointerException | IndexOutOfBoundsException e) {
e.printStackTrace();
}
System.out.println(max);
}
public static int getMax(int[] arr) /*throws NullPointerException, IndexOutOfBoundsException*/
{
if(arr == null)
{
//手动创建一个异常对象,并把这个异常交给方法的调用者处理
//此时这个方法就会结束,下面的代码不会执行了。
throw new NullPointerException();
}
if(arr.length == 0)
{
throw new IndexOutOfBoundsException();
}
int max = arr[0];
for (int i = 0; i < arr.length; i++) {
if(arr[i] > max)
max = arr[i];
}
return max;
}
}
一个对输入进行检验并循环输入直到无误的例子
Scanner sc = new Scanner(System.in);
GrilFriend gf = new GrilFriend();
while(true)
{
try
{
System.out.printlf("请输入女朋友的名字");
String name = sc.nextLine();
gf.setName(name);//setName方法中写了关于检验数据并抛出异常的代码
System.out.printlf("请输入女朋友的年龄");
String ageStr = sc.nextLine();
int age = Integer.parseInt(ageStr);
gf.setAge(age);//setAge方法中写了关于检验数据并抛出异常的代码
//如果以上数据都输入正确,那么跳出循环
break;
}
catch(NumberFormatException e)
{
//int age = Integer.parseInt(ageStr);可能抛出的异常
System.out.printlf("年龄的格式有误,请输入数字");
continue;//可省略,因为try语句后没有其他的代码了
}
catch(RuntimeException e)//父类异常记得写在下面
{
//setName或setAge可能抛出的异常
System.out.printlf("姓名的长度或者年龄的范围有误");
continue;
}
}
//打印
System.out.printlf(gf);
自定义异常
意义:让控制台的报错信息更加的见名知意
步骤:
- 定义异常类,见名知意。
- 写继承关系,运行时异常要继承RuntimeException,编译时异常直接继承Exception即可。
- 写空参构造、带参构造。
public class NameFormatException extends
{
//命名技巧:
//NameFormat:当前异常的名字,表示姓名格式化问题
//Exception:表示当前类是一个异常类
//运行时异常要 extends RuntimeException 描述由于参数错误而导致的问题
//编译时异常要 extends Exception 提醒程序员检查本地信息
//写k
public NameFormatException(){
}
public NameFormatException(String message){
super(message);
}
}