面向对象
- 一个
.java
文件可以有N个类,但只能有一个public
类 - 类包含属性(特征)和方法(行为)
- 函数的返回
void say(){ if (true){ return; // 如果进入这里,return表示方法执行结束,无需向下执行,不返回任何值(也不能) } System.out.println("false"); }
- 栈内存:通过栈指针来创建空间和释放空间,速度很快!
- 栈中存储对象/变量名称,并指向堆中的对象
- 堆内存:存放类的实例(对象)
- Java是纯面向对象语言,类的对象必须是通过
new
创建
- Java是纯面向对象语言,类的对象必须是通过
- 方法区:类中包含的方法会放在方法区
- 因为方法都是一样的,不需要每次都实例化;通过堆中的对象调用,传入堆中的属性即可
- 对象使用完毕栈会先后释放,堆中的对象没有引用指向,符合GC机制,被回收!
- 如果定义了有参数构造方法,必须自定义无参构造
- 建议任何时候都自定义无参和全参构造方法
方法重载
- 类中定义的方法,如果要重载,需满足:
- 方法名称相同
- 参数列表长度或参数列表类型不同,或者参数类型的顺序不同
class Mathematics{ int sum(int x, int y){ return x+y; } double sum(int x, double y){ return x+y; } double sum(double x, int y){ return x+y; } }
- 最常见的是构造方法的重载,各种参数长度
- 匿名对象:不给变量接收,只用一次
编程规约
- 命名风格
三大特性
封装
- 隐藏对象的属性和实现细节,只暴露出公共的方法,里面可以添加约束
- 一般就是讲属性设置为
private
- 设置
set
和get
方法;IDEA右键—Generate—Getter and Setter
- 一般就是讲属性设置为
- 构造方法中,如果参数和属性不重名,编译器默认隐藏了
this
(表示当前对象)- 构造方法只需要
public
,不需要返回值类型
class Person{ private String name; private int age; Person(){ this("Roy", 22); // 调用自己的有参构造(不推荐) } Person(String name, int age){ this.name = name; this.age = age; } }
- 构造方法只需要
- 使用
static
修饰的属性和方法都存放在公共方法区- 可以将
private
修饰的属性理解为对象属性/对象方法 - 静态属性理解为类属性/类方法,在类加载时创建一份,可直接使用类名调用
- 优点:对象可以创建多个,类只有一份,可以放置所有对象的公共属性/方法,节省内存,
class Student{ private String name; private static String region; // 学生地区,都是一样的 Student(String name){ this.name = name; } public static void say(){ // 或者get方法 System.out.println("这是static方法!"); // return region; } public static void main(String[] args) { Student.say(); } }
- 可以将
- 类先加载,对象才创建,所以对象方法可以调用静态方法,反之不行!
包
- 把功能相同或相关的类或接口写在同一个包
package
中,例如可以叫做com.roykun
- 不同包的类名可以相同,导入类需要加上包名作为区分,例如
import com.roykun.Students;
- 包也可以限定访问权限
- 有一些类不需要导包,例如
package java.lang;
,经常使用,系统自动导入;当然,使用当前包中的其他类也不需要
构造代码块
- 随着对象的创建,执行一次,且在构造方法之前
- 构造方法不一定执行(重载),构造代码块一定会执行
- 构造代码块可以有多个
class GouZao{ private String name; { System.out.println("构造代码块1"); } { System.out.println("构造代码块2"); } GouZao(){ System.out.println("构造方法"); } public static void main(String[] args) { GouZao gz = new GouZao(); } } // 构造代码块1 // 构造代码块2 // 构造方法
- 静态代码块
- 只会随着类的加载,执行一次,而且先于构造代码块
static { System.out.println("静态代码块"); }
- 输出错误提示可以使用:
System.err.println("页数不能少于200~");
继承
- 继承就是子类继承父类的属性和行为,方便代码的复用
- Java中只有单继承、多重继承,没有多继承
- 子类实例化时,会先创建父类(先执行的还是子类的构造,但是隐藏了第一行的
super();
),本质是子类拥有了父类的一个地址- 子类创建时,默认使用父类的无参构造方法创建父类
- 如果在子类构造方法中制定父类的其他构造方法,需要写在第一行
- 子类能够直接使用父类
protected
和public
修饰的属性和方法,或者通过提供的set方法
重写
- 重载(overload):一个类中,同名方法,参数长度、类型或顺序不同
- 重写(override):
- 发生在子类和父类中
- 参数列表必须完全相同
- 返回类型必须相同
- 访问权限不能比父类中的权限低,例如父类使用public,子类就不能用
protected
(越开放权限越大) - 父类的成员方法只能被他的子类重写
- 声明为static和private的方法不能重写,但能够再次声明
- 从实际开发来讲,
static
没必要重写 private
是因为权限子类拿不到,重写个锤子
- 从实际开发来讲,
final
- 用于修饰属性:
- 修饰的局部变量只能赋值一次
- 修饰的成员属性,必须在声明时赋值
class Stu{ final int a=0; public static void main(String[] args) { final int a; } } public static final; // 全局常量
- 简而言之,将变量变为常量
- 命名规范:所有单词大写,单词间用下划线隔开
- 用于修饰类
- 说明这个类不能被继承
- 用于修饰方法
- 不能被子类重写
抽象
- 抽象类
public abstract class Person { public abstract void abc(); // 抽象方法 }
- 继承抽象类,必须实现里面的所有抽象方法
import com.book.Person; public class PersonTest extends Person { @Override public void abc() { System.out.println("实现抽象方法"); } }
- 抽象类不能实例化(不能new),因为没有定义内容啊
final
不能修饰抽象类,因为抽象类必须被继承才有意义(只能public或者protected)- 抽象类也能有构造方法
- 好处,只定义方法名称,具体内容交给子类实现,让父类更通用更简洁
接口
- 类中全部都是抽象方法,属性都是全局常量
- 面向接口编程:定义与实现的分离(解耦)
- 降低程序耦合性
- 易于程序的扩展
- 有利于程序的维护
- 因为都是全局常量,所以可以省略写
public static final
- 因为都是抽象方法,可以省略
public abstract
public interface Person{ // 文件名必须是Person.java int a= 0; void say(); }
- 继承接口:
implement
public class PersonTest implements Person {
@Override
public void say() {
System.out.println("实现接口方法");
}
}
- 和抽象类的区别:
- 简而言之,抽象类和普通类差别不大,但是接口纯抽象,纯常量
多态
- 条件:有继承,有重写,父类引用指向子类对象
- 举个例子:
// Person.java public class Person { private String name; private int age; public void show(){ System.out.println("Person"); } } // Students.java public class Students extends Person{ // 继承 private String name; private int age; public void show(){ // 重写 System.out.println("Students"); } } // Demo.java public class Demo { public static void main(String[] args) { Students s = new Students(); say(s); // Students 执行的是子类的方法 // 只要是继承了父类,都可以传 } public static void say(Person p){ // 全局函数:传递的是父类的引用,父类引用指向子类对象 p.show(); // say() 传父用子 } }
- 类有父类和子类之分,子类就是父类的其他形态,体现了对象的多态性
- 方法的重载和重写也是一种多态的体现,即同名方法的不同形态
- 多态的好处有很多
- 符合面向接口编程的思想,各子类只需继承父类,单独设计好,自定义的子类就能作为参数向接口传递,不必修改全局函数的代码(传父用子)
- Java是单继承的(只能extends一个父类)
- Java是多实现的(可以implements多个接口,因为是全局常量抽象方法,不用担心调用冲突)
- 这里有可能需要判断传入的类型,避免转型出错
if (p instanceof Students){ // p是Students类型 Students s = (Students)p; // 强转 }
- 当 p 为 Students 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果都返回 true
Object
- 所有类的父类
- 作为形参,可以用来接收任意的引用类型数据
- 重写
toString()
方法
@Override public String toString() { return "Students{" + "name='" + name + '\'' + ", age=" + age + '}'; }
equals()
方法==
比较的是内存地址,但是equals方法默认就是用这个,一般得重写
// Person.java public boolean equals(Object o){ if (this == o){ return true; // 内存如果相同必相等 } if (o == null){ return false; // 非空 } if (o instanceof Person){ Person p = (Person)o; // 转型到Person比较(有可能是其子类) if (this.name.equals(p.name) && this.age == p.age){ // 需要根据业务调整 return true; } } return false; } // 自动生成 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; // 涉及到反射 Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); }
- 后面会说到转型的问题
API
- JDK11API中文最新版
内部类
- 用的不多,将一个类定义在另一个类的内部
- 成员内部类
- 局部内部类
- 匿名内部类
- 静态内部类
- 成员内部类
- 可以无条件访问所有外部类的属性和方法
- 如果和外部类同名,默认访问内部类方法
// Outer public class Outer { private int x; public int getX() { return x; } public void setX(int x) { this.x = x; } class Inner{ public void show(){ System.out.println(x); // 默认打印自己的 System.out.println(Outer.this.x); } } } // Demo public class Demo { public static void main(String[] args) { Outer o = new Outer(); o.setX(110); Outer.Inner i = o.new Inner(); i.show(); // 200 110 } }
- 局部内部类
- 定义在方法里的类,一般是这个类就用一次,才这么干
- 匿名内部类:没有名字,直接跟在父类或接口后面定义
Person p = new Person(){ int a = 10; public void say(){ System.out.println(匿名内部类方法); } }
- 不能是抽象的,不能定义构造函数
- 只能访问final型的局部变量
- 静态内部类
- 内部类class前多加了个
static
关键字 - 不需要依赖外部类的对象,即可以不创建外部类,也不能使用外部类的非static属性和方法
- 内部类class前多加了个
包装类型
- 基本数据类型声明时直接在栈中,其他的(引用、数据结构、数据类型)都属于类,会创建对象在堆中
- 对基本类型封装了对应的包装类型
Integer/Long/Double/Short/Float/Byte
是java.lang.Number
的子类Character/Boolean
直接是Object
的子类- 装箱/拆箱
public static void main(String[] args) { Integer i = new Integer(100); // 装箱 int a = i.intValue(); // 拆箱 Integer b = 200; // 自动装箱 int c = b; // 自动拆箱 }
- 包装类的
parse
方法,可以将一个字符串变为指定的基本数据类型(得是相关字符串)
Scanner in = new Scanner(System.in); String s = in.nextLine(); // 字符串 100 int x = Integer.parseInt(s); // 提取出里面的数字 System.out.println(x+1);
- 包装类的
可变参数
- 参数长度不固定,使用
...
表示(无限制)public static void sum(int... nums){ int n = 0; for (int i=0; i<nums.length; i++){ n += nums[i]; } System.out.println(n); }
- 可变参数必须在其他参数的后面
递归
- 实现阶乘
public static int jiecheng(int n) { if (n==1){ return 1; }else { return n*jiecheng(n-1); // 5*4!... } }
- 很好理解,每次函数的调用,只看它最终会得到的结果(就一个数),别去想它的过程
异常
- 在API里搜索Exception就能看到所有的异常类
- 非受检异常:例如
RuntimeException
,运行时传递的参数等不定原因引起的,JVM会自动处理,也可以捕获 - 受检异常:必须抛出或捕获,明确处理方式,否则程序无法运行
- 非受检异常:例如
- 捕获异常
- JVM会根据异常的情况,创建对象,包含了异常信息
- 出现问题,为了不返回给调用的方法而引起程序终止,需要捕获
public class Exp { public static void main(String[] args) { Scanner s = new Scanner(System.in); System.out.println("输入被除数:"); int a = s.nextInt(); System.out.println("输入除数:"); int b = s.nextInt(); try { System.out.println(a/b); System.out.println("计算完毕!"); }catch (NumberFormatException e){ // System.out.println(e.getMessage()); System.out.println("除数不能为0"); }finally { System.out.println("程序结束..."); } } }
- 可以设置多个
catch
块捕获异常,也可以用|
的形式写,也可以直接使用RuntimeException
(多态) finally
必执行,一般用来手动释放资源
- 注意这里有个常考点:
public static Person show2(){ Person p = new Person(); try { p.age = 22; return p; }catch (RuntimeException e){ return null; }finally { p.age = 18; } } public static int show1(){ int a = 10; try { return a; }catch (RuntimeException e){ return 0; }finally { a = 20; } } public static void main(String[] args) { System.out.println(show1()); // 10 System.out.println(show2().age); // 18 }
- 这里finally中的代码是否会改变返回结果,得看操作的对象是栈中的普通变量,还是堆中的引用
return
会复制栈中我们要返回的内容,如果是引用,那复制的就是内存地址,改了p.age
,还是会改变最终结果;如果复制的只是一个值,自然不会变!
抛出
- 一般捕获了异常都是要处理的,但有时需要抛出,因为不是这里代码的问题
public static void shut(String text) throws IOException { Runtime.getRuntime().exec(text); // 这里传参不对可能异常 }
- 如果是因为传参导致,应该通过
throws
将异常抛出去,用户的问题 - 也可以自己实例化异常抛出去,用的很少