第二章 : 面向对象(OOP)
成员变量和局部变量的区别
- 作用域不同:局部变量只能在方法体内使用,成员变量能在整个类内使用。
- 定义的位置不同:局部变量在方法体内定义,成员变量在类内定义。
- 初始值不同:局部变量使用必须手动初始化,成员变量如果不手动初始化,会被自动初始化为默认值。
- 内存中的位置不一样:局部变量在JVM栈中,成员变量在堆中。
- 生命周期不同:局部变量随方法结束被回收,成员变量随对象被回收而结束。
OOP三大特征:封装继承和多态
1. 封装
1.1 什么是封装
两种封装性的体现:
- 方法 : 对实现细节的隐藏
- private : 对成员变量的保护
tips:boolean类型的get方法名字应该是isXxx(),而不是getXxx()。
1.2 什么是一个标准的类
- 所有成员变量均是private修饰。
- 所有成员变量均有一个get和set方法。
- 有一个无参构造方法。
- 有一个全参数构造方法。
2. 常用API
2.1 Scanner类
import java.util.Scanner;
// System.in 表示从键盘,也就是标准输入流获取输入
Scanner sc = new Scanner(System.in);
sc.close();
-
next() 获取下一个单词,空格或者空白字符区分。
-
hasNext() 下面是否还有输入,空格或者空白字符分开。
-
nextLine() 获取下一行。用回车区分。
-
hasNextLine() 下面是否还有一行输入。用回车区分。
-
nextInt() 获取下一个整数。
-
hasNextInt() 下一个是否可以转成int。
2.2 Random类
用法:生成随机数
import java.util.Random;
Random rd = new Random;
//输出一个int范围内的整数
sout(rd.nextInt());
//输出一个[0,14)内的整数
sout(rd.nextInt(15))
2.3 ArrayList
动态数组,类似于vector。
2.3.1 常用方法
- 初始化 :
ArrayList<E> list = new ArrayList<>();
- 输出 : 直接sout的话,输出的是内容。而不是ArrayList的内存地址。
- size() : 返回长度。
- add(E) : 添加内容,类似于vector的push_back()。
- add(index, E) : 在指定的位置插入元素。
- remove(index) : 根据index删除元素,返回元素值。如果index越界,会抛出异常。
- remove(O) : 删除第一次出现的O,返回布尔值。如果O不存在,返回false,不会抛出异常。
- get(index) : 根据index取对应位置的元素。
- indexOf(O) : 根据元素内容找第一次出现的位置。
Tips: ArrayList中只能存储引用类型,不能存储基本类型。
基本类型 | 包装类 |
---|---|
int | Integer |
char | Character |
byte | Byte |
short | Short |
long | Long |
float | Float |
double | Double |
2.3.2 ArrayList<int[]> 转二维数组
ArrayList<int[]> list = new ArrayList<>();
list.add(new int[]{1,2});
list.add(new int[]{5,6});
list.add(new int[]{9,9});
//ArrayList<int[]>转二维数组。
int[][] r = list.toArray(new int[0][]);
//Arrays.deepToString()可以将二维数组转为字符串。
System.out.println(Arrays.deepToString(r));
2.4 字符串String
字符串的特点:
- 字符串是常量,不可变。
- 字符串是共享的,只要字符串内容一样,就是同一个字符串。
字符串的构造方法:
- String(), 创建一个空白字符串。
- String(char[] array), 从字符数组中创建。
- String(byte[] array), 从字节数组中创建。比如ASCII码,{97,98,99},对应创建出来的字符串就是"abc"。
- 直接创建。
String str = "hello"。
字符串的常量池:
- 只要是双引号直接写出来的字符串,就在字符串常量池中。字符串常量池是堆中的一块空间。
- 自己new的String对象,在池外,堆里。
- 注意,对于引用类型,==是进行地址比较。
String s1 = "abc";
String s2 = "abc";
sout(s1 == s2);
//结果是true。
String s3 = new String("abc");
System.out.println(s2==s3);
//结果是false
字符串常用方法:
- equals(O),只有O是内容相同的字符串,才会返回true。
如果是字符串常量和变量相比较,推荐"abc".equals(str)。因为str可能是null,为了防止空指针异常。
- equalsIgnoreCase(O),同上,忽略大小写。
- length(), 获取长度。
- concat(str), 返回拼接的字符串。
- charAt(index), 返回索引位置的char字符
- indexOf(str/char), 返回子串首次出现的位置,如果没有,返回-1。
- substring(beginIndex, endIndex),返回[begin,end)的子串。左闭右开。
- toCharArray(), 返回字符串拆出来的字符数组。
- getBytes(), 返回字符串对应的字节数组。
- replace(oldstr,newstr), 返回将oldstr全部替换为newstr的字符串。
- replaceFirst(oldstr,newstr), 返回将第一个oldstr替换为newstr的字符串。
- endWith(str), 返回是否以给定的字符串结尾。
字符串分割:
- split(regex), 按照给定的规则"正则表达式"将字符串分割为多个字符串,返回字符串数组String[]。
2.5 static 修饰符
-
修饰成员变量 : 使用static修饰成员变量后,变量就不属于某个对象独有了,全部对象共享同一个数据。
-
修饰成员方法 : 使用static修饰成员方法后,就变成了静态方法。静态方法不需要创建对象就可以直接通过类名称来调用。就算用对象名来调用静态方法,也会被javac编译为通过类名称来调用。
-
静态方法只能访问静态变量/方法,不能访问非静态变量/方法。
-
静态方法中不能使用this。
-
内存中先初始化静态对象与静态方法。
-
静态对象存储在方法区中。
静态代码块
首次创建类对象时,执行唯一一次静态代码块。第二次创建对象时就不执行了。
经典用法:一次性地对静态成员变量赋值。
public class MyClass{
static{
//静态代码块
//静态代码块比其他方法执行优先度都高。
}
}
2.6 Arrays类
数组相关的工具类,里面提供了大量的静态方法,用来实现数组的常见操作。
- toString(数组),按照默认格式,将数组输出为字符串。
- sort(数组,[lambda表达式]),使用lambda表达式对数组进行排序。默认升序/字典序升序。
如果是自定义类型,需要实现Comparable或者Comparator接口
2.7 Math类
- abs(), 返回double类型。
- ceil(), 向上取整。返回double。
- floor(), 向下取整。返回double。
- round(), 四舍五入。返回long。
- Math.PI, 就是π。
2.8. 匿名对象
匿名对象只能使用一次。
使用建议:如果确定某个对象只需要使用唯一的一次,那么就可以使用匿名对象。
//只想使用一次标准输入的情况下,可以使用匿名Scanner对象
int number = new Scanner(System.in).nextInt();
sout(number);
//使用匿名对象传参
fun(new Scanner(System.in));
//被调函数
public static void fun(Scanner sc){
sout(sc.nextInt());
}
3. 继承
继承是多态的前提,没有继承就没有多态。
- this.val: 本类的变量
- super.val: 父类的变量
3.1 @Override重写
子类中方法名一样,参数列表也一样
- 注意:子类方法的返回值必须小于等于父类方法的返回值类型
//父类返回值为Object
public Object method(){};
//子类返回值为String
public String method(){};
- 注意:子类方法的权限必须大于等于父类方法的权限修饰符
//default是什么都不写,留空。
public>protected>(default)>private
- 注意:重写时,如果要用到父类方法的功能,不用重写一遍,写super.method()即可
3.2 继承里的构造函数
- 先执行父类构造函数,再执行子类构造函数。
- 子类构造函数里会默认包含一个super()函数,即父类无参构造函数。
- 只有子类构造函数才能调用super(),而且super()必须写在子类构造函数第一行。
3.3 super的三类用法
- 子类的成员方法中访问父类的成员变量。
- 子类的成员方法中访问父类的成员方法。
- 子类的构造方法中访问父类的构造方法。
3.4 this的三类用法
- 本类成员方法中访问本类成员变量。
- 本类成员方法中访问另一个成员方法。
- 本类的构造方法中访问本类的另一个构造方法。
this();
注意:this()和super()不能同时使用。
3.5 内存中的子类
在内存中子类包含了一个完整的父类内容。
3.6 继承的三大特征
- Java继承是单继承,C++支持多继承。
- Java可以多级继承。A->B->C。
- 一个父类可以有多个子类。
4. 抽象类Abstract
4.1 基本概念
- 抽象方法
public abstract void fun();
- 抽象方法所在的类必须是抽象类
public abstract class A(){
}
- 抽象类可以有普通方法。
4.2 抽象类的使用
- 抽象类不能直接new对象。
- 抽象类必须用一个子类来继承。
- 抽象类必须被实现父类的所有的抽象方法。
- 抽象类不一定有抽象方法。
注意:没有抽象方法的抽象类是在一些特殊场景下有用途,比如适配器模式。
5. 接口
接口是一种公共的规范标准。
5.1 接口定义的基本格式
- 接口是一种引用数据类型。最重要的内容就是其中的抽象方法。
- 接口没有构造方法
- 接口没有静态代码块
public interface A{
}
5.2 接口中可以包含的内容
- 常量
- 抽象方法
- 默认方法(Java 8)
- 静态方法(Java 8)
- 私有方法(Java 9)
5.2.1 接口中的抽象方法
- 抽象方法必须是public abstract修饰,也可以忽略不写。
public interface MyInterfaceAbstract {
public abstract void printHello1();
public void printHello2();
abstract void printHello3();
void printHello4();
}
5.2.2 接口的实现
- implements关键字来实现接口,并实现接口中的所有抽象类。
public class ImplMyInterfaceAbs implements MyInterfaceAbstract{
@Override
public void printHello() {
System.out.println("Hello world!");
}
}
- 如果没实现所有的抽象方法,那么这个类就必须是抽象类。
5.2.3 默认方法(Java 8)
- 默认方法可以有方法体。
- 默认方法是为了解决接口升级的问题。
- public可以省略,default不能省略。
- 默认方法会被接口的实现类继承下去。
- 默认方法也可以被接口的实现类Override。
public interface MyInterfaceAbstract {
void printHello();
public default void print2(){
System.out.println("我是接口的默认方法");
}
}
public class ImplMyInterfaceAbs implements MyInterfaceAbstract{
@Override
public void printHello() {
System.out.println("Hello world!");
}
}
public class test {
public static void main(String[] args) {
ImplMyInterfaceAbs inter = new ImplMyInterfaceAbs();
inter.printHello();
inter.print2();
}
}
//=========================
/* 输出如下:
Hello world!
我是接口的默认方法
*/
5.2.4 接口的静态方法(Java 8)
- 接口的静态方法只能通过接口来直接调用
- 接口的静态方法不能通过接口的实现类来调用
public interface MyInterfaceAbstract {
void printHello();
public default void print2(){
System.out.println("我是接口的默认方法");
}
public static void static_fun(){
System.out.println("我是接口的静态方法");
}
}
public class ImplMyInterfaceAbs implements MyInterfaceAbstract{
@Override
public void printHello() {
System.out.println("Hello world!");
}
}
public class test {
public static void main(String[] args) {
ImplMyInterfaceAbs inter = new ImplMyInterfaceAbs();
inter.printHello();
inter.print2();
//inter.static_fun(); 不能这么用,会报错。
MyInterfaceAbstract.static_fun();
}
}
//=========================
/* 输出如下:
Hello world!
我是接口的默认方法
我是接口的静态方法
*/
5.2.5 接口的私有方法(Java 9)
为了解决默认方法中的重复代码问题
- 普通私有方法,解决多个默认方法的代码重复问题。
private void fun1(){
//TODO
}
- 静态私有方法,解决多个静态方法的代码重复问题。
5.2.6 接口的常量
使用public static final来修饰
- 一旦使用final修饰,变量就不可变。
- 接口中的常量默认就是public static final
- 接口中的常量必须显式赋值。
- 常量名字要全部大写。
public static final int NUM = 10;
5.3 接口实现注意事项
- 一个类可以实现多个接口。
- 如果实现的两个接口的抽象方法重名了,只需@Override一次即可。
- 如果实现的两个接口的默认方法重名了,则必须@Override。
- 如果继承的类的方法和实现的接口的默认方法重名了,继承优先于接口实现。
5.4 接口之间的多继承
- 类与类之间是单继承的。一个类只能继承一个类。
- 类与接口是多实现的。一个类可以实现多个接口。
- 接口与接口之间是多继承的。
package interfaceDemo;
public interface MyInterfaceA {
public abstract void methodA();
public abstract void methodCommon();
}
public interface MyInterfaceB {
public abstract void methodB();
public abstract void methodCommon();
}
public interface MyInterface extends MyInterfaceA,MyInterfaceB {
public abstract void method();
}
/*
此时MyInterface一共有4个抽象方法。分别为:
public abstract void method();
public abstract void methodA();
public abstract void methodB();
public abstract void methodCommon();
*/
注意:
1. 多个父接口的抽象方法重复了没关系,因为都是抽象方法,并没有被实现。
2. 多个父接口的默认方法如果重复,则子接口必须实现这个默认方法,还得带着default关键字。
6. 多态
6.1 多态的定义
- 多态性的前提,是extends或者implements。
- 一个对象拥有多种形态,就是对象的多态性。
6.2 代码中体现多态性
-
代码中的多态性,就是 父类引用指向子类对象。
父类名字 对象名 = new 子类名字();
接口名称 对象名 = new 实现类的名字();
-
代码演示如下,可见父类引用指向了子类对象。
package multiDemo;
public class Fu {
int num = 10;
public void fun(){
System.out.println("父类名称");
}
}
public class Zi extends Fu{
int num = 20;
int age = 30;
@Override
public void fun() {
System.out.println("子类方法");
}
}
public class test {
public static void main(String[] args) {
//new的谁就是谁的方法。
//如果子类没@Override这个方法,就会往上找。
Fu ins = new Zi();
ins.fun();
}
}
/*输出如下:
子类方法
*/
6.3 多态中成员变量的使用方法
- 直接通过对象名称使用成员变量,等号左边是谁就是谁。没有就向上找。
- 间接通过成员方法来使用成员变量。该方法属于谁就用谁,没有就向上找。
- 编译看左,运行看左。
//代码同上
Fu ins = new Zi()
System.out.println(ins.num);
//下面这句会报错,因为Fu类没有age成员变量。
//System.out.println(ins.age);
//输出结果为:10
6.4 多态中成员方法的使用方法
- new的谁,就优先用谁,没有就向上找。
- 编译看左,运行看右。
6.5 多态有什么用
无论右边new的时候换成哪个子类对象,等号左边调用方法都不会变。
6.6 对象的向上转型
-
多态写法,就是对象的向上转型。
父类引用指向子类对象
父类名称 对象名 = new 子类名称();
含义:创建一个子类对象,当做父类使用。 -
向上转型一定是安全的。
小范围转向大范围,比如int转向Integer;比如int转向double。 -
向上转型的弊端:对象无法调用子类特有方法。
6.7 对象的向下转型(还原)
- 子类名称 子类对象 = (子类名称) 父类对象
- 如果父类对象之前不是子类对象向上转型的,就不能转换下来。
- 错误的类型转换编译不会报错,运行会报错,java.lang.ClassCastException。
6.8 怎么知道父类引用指向的子类名字呢?
- 对象名 Instanceof 类名称
- 返回boolean类型。
- 注意: 如果Instanceof两边的类不存在继承关系,那么不能使用。
7. final关键字
7.1 用来修饰一个类
-
表示当前类不能有任何子类。
public final class MyClass{ }
7.2 用来修饰一个方法
- 子类无法@Override被final修饰的父类方法。
public final void fun(){}
- abstract和final无法同时使用
7.3 用来修饰一个局部变量
- 被final修饰的局部变量不可变。
- 要保证被final修饰的局部变量只有唯一的一次赋值行为。
final int a = 10;
final int b;
b = 12;
-
不可变:
对于基本数据类型,指的是数据不可变。
对于引用类型,指的是变量当中的内存地址不可变。但是地址指向的对象的内容可变。比如学生类的实例,实例的学生名字可变,但是不能指向别的学生。
7.4 用来修饰一个成员变量
- 因为成员变量具有默认值,所以final修饰的成员变量必须手动赋值。
- 赋值方法:直接赋值;构造函数赋值。
- 必须保证类的所有重载的构造函数都对final修饰的成员变量进行赋值。
public class student {
private final String name;
//默认无参构造函数赋值默认的名字
public student() {
name = "没名字";
}
//带参构造函数对name进行赋值
public student(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
8. 内部类
一个类内部包含另一个类。
比如汽车类和发动机类,汽车包含发动机,所以发动机是汽车的内部类。发动机无法单独存在,必须在汽车内部才有用。
8.1 成员内部类
8.1.1 定义格式
修饰符 class 外部类名称{
修饰符 class 内部类名称{
}
}
8.1.2 使用方式
-
内部类用外部类成员变量,可以随意使用,外用内,一定需要借助内部类对象。
-
内部类的两种使用方式:
- 间接使用。在外部类的方法中调用内部类,main函数中调用外部方法。
- 直接使用。
外部类.内部类 内部类对象名 = new 外部类().new 内部类()
//演示
package InnerClassDemo;
public class Body {
public class Heart {
private int heartWeight;
public int getHeartWeight() {
return heartWeight;
}
public void setHeartWeight(int heartWeight) {
this.heartWeight = heartWeight;
}
public Heart(int heartWeight) {
this.heartWeight = heartWeight;
}
public void printBodyWeight() {
System.out.println("心脏内部类中查看外部类的体重:" + weight);
}
public void printHeartWeight() {
System.out.println("心脏内部类中查看自己的心脏重量:" + heartWeight);
}
public void methodHeart() {
System.out.println("内部类方法");
}
}
private int weight;
private final Heart myheart = new Heart(1);
public Body(int weight) {
this.weight = weight;
}
public void getWeight() {
System.out.println("外部身体类中查看身体重量:" + this.weight);
}
public void setWeight(int weight) {
this.weight = weight;
}
public void methodBody() {
System.out.println("外部类方法");
}
public void printHeartWeight() {
System.out.println("外部身体类中打印心脏内部类的重量:" + myheart.heartWeight);
}
public void printHeartMethod() {
System.out.println("=======集中调用心脏内部类的方法=======");
myheart.methodHeart();
myheart.printBodyWeight();
myheart.printHeartWeight();
}
}
//====================//
package InnerClassDemo;
public class test {
public static void main(String[] args) {
Body mybody = new Body(50);
mybody.methodBody();
mybody.getWeight();
mybody.printHeartWeight();
mybody.printHeartMethod();
System.out.println("============");
Body.Heart myHeart = new Body(40).new Heart(1);
myHeart.printHeartWeight();
}
}
//============输出结果============//
外部类方法
外部身体类中查看身体重量:50
外部身体类中打印心脏内部类的重量:1
=======集中调用心脏内部类的方法=======
内部类方法
心脏内部类中查看外部类的体重:50
心脏内部类中查看自己的心脏重量:1
============
心脏内部类中查看自己的心脏重量:1
8.1.3 同名变量的访问
- 如果内部类成员变量和外部类成员变量名字重复了,通过
外部类.this.成员变量
来访问。
package InnerClassDemo;
public class Outer {
//外部类的成员变量
private int num = 10;
public class Inner {
//内部类的成员变量
private int num = 20;
public void methodInner() {
//内部类的成员方法内局部变量
int num = 30;
System.out.println(num); // 局部变量:30
System.out.println(this.num); //内部类的num:20
System.out.println(Outer.this.num); //外部类的成员变量:10
}
}
}
package InnerClassDemo;
public class test {
public static void main(String[] args) {
Outer.Inner inner = new Outer().new Inner();
inner.methodInner();
}
}
//============输出结果============//
30
20
10
8.2 局部内部类(包含匿名内部类)
- 如果一个内部类是定义在方法内部的,就是局部内部类。
- 局部内部类什么修饰符都不用加
public void fun(){
class Inner{
// ...
}
}
8.3 局部内部类的final问题
局部内部类内如果想访问方法内的局部变量,这个变量必须是有效的final变量
原因分析:
- 局部内部类对象是new出来的,存在堆里。
- 方法的局部变量储存在栈上。
- 一旦方法执行结束,局部变量会被立刻清空。
- 局部内部类对象会等待垃圾回收,生命周期比局部变量长。
- 所以局部内部类使用的方法内的局部变量需要是有效final,储存在常量池。
public class LocalInnerClass {
public void method(){
int num = 10;
final int i = 30;
/*
Variable 'num' is accessed from within inner class, needs to be final or effectively final
*/
//错误写法,因为num被局部内部类使用,不能更改。
//num = 20;
class Inner{
public void innerMethod(){
System.out.println(num);
System.out.println(i);
}
}
}
}
8.4 匿名内部类
如果接口的实现类,或者父类的子类只需要使用一次,那么就可以通过匿名内部类的方式来创建对象。
定义方式
接口名称 对象名称 = new 接口名称() {//...};
package AnonymousInnerClassDemo;
public interface MyInterface {
public abstract void method();
}
public class test {
public static void main(String[] args) {
MyInterface obj = new MyInterface() {
@Override
public void method() {
System.out.println("匿名内部类实现接口");
}
};
obj.method();
}
}
//==========输出============//
匿名内部类实现接口
匿名内部类的匿名对象
package AnonymousInnerClassDemo;
public interface MyInterface {
public abstract void method();
}
public class test {
public static void main(String[] args) {
new MyInterface() {
@Override
public void method() {
System.out.println("匿名内部类的匿名对象实现接口");
}
}.method();
}
}
//==========输出============//
匿名内部类的匿名对象实现接口
9 类和接口作为各种参数
9.1 类作为成员变量类型
比如String,就是类,String对象可以作为成员变量。
9.2 接口作为成员变量类型
- 权限修饰符 接口名 对象名;
- 传参数进去时,可以是实现一下接口的类的对象,也可以使用匿名内部类,也可以不新建对象,使用匿名对象。
package InterfaceAsParam;
//接口定义
public interface MyInterface {
void printMessage();
}
//接口的实现类
public class ImplMyInterface implements MyInterface{
@Override
public void printMessage() {
System.out.println("我是通过类实现了接口。");
}
}
//使用接口作为成员变量的类
public class InterfaceAsMemberVar {
private MyInterface myInterface;
public void method(){
//调用接口的抽象方法
myInterface.printMessage();
}
public InterfaceAsMemberVar(MyInterface myInterface) {
this.myInterface = myInterface;
}
}
//测试函数
public class test {
public static void main(String[] args) {
InterfaceAsMemberVar obj_1 = new InterfaceAsMemberVar(new ImplMyInterface());
obj_1.method();
MyInterface myInterface = new MyInterface() {
@Override
public void printMessage() {
System.out.println("我通过匿名内部类实现了接口");
}
};
InterfaceAsMemberVar obj_2 = new InterfaceAsMemberVar(myInterface);
obj_2.method();
InterfaceAsMemberVar obj_3 = new InterfaceAsMemberVar(new MyInterface() {
@Override
public void printMessage() {
System.out.println("我通过匿名内部类的匿名对象实现了接口");
}
});
obj_3.method();
}
}
//==========输出==========//
我是通过类实现了接口。
我通过匿名内部类实现了接口
我通过匿名内部类的匿名对象实现了接口
9.3 接口作为方法的参数或者返回值。
-
比如List这个接口
List<String> l = new ArrayList<>();