二、面向对象基础
1.对象(object,instance):在内存中是一个内存块,用来表示相关联的数据,其中包括对象的属性和方法。
2.类(class):可以看作对象的模板,是对象的抽象。
3.一个类可以是另一个类的属性。
4.内存分析:
java虚拟机的内存可以分为三个区域:栈stack,堆heap,方法区method area。
栈:
- 栈描述的是方法执行的内存模型,每个方法被调用都会创建一个栈帧(局部变量,操作数,方法出口)。当一个方法被调用的时候该方法进栈,当该方法中又会调用其他方法时,再把其他方法进栈。其他方法执行完之后出栈,该方法继续执行。
- JVM会为每一个线程创建一个栈。线程执行完之后,这个栈就被关闭。
- 栈属于线程私有,不能实现线程间的共享。
- 先进后出,后进先出。
- 栈是系统自动分配,速度很快,是一个连续的内存空间。
堆 - JVM只有一个堆,被所有线程共享。
- 用于存储创建好的对象和数组(数组也是对象)。(new。。。就是放在堆里)
- 是一个不连续的空间,分配灵活但是速度慢。
方法区(静态区) - JVM只有一个方法堆,被所有线程共享。
- 实际上也是堆,只是用于存储类,常量相关的信息。
- 用来存放程序中永远不变或唯一的内容。(类信息,静态变量,字符串常量)
5.构造方法/构造器(constructor),用于对象的初始化。
- 构造方法和类名称一致,通过New关键调用。
- 虽然有返回值 ,但是不需要声明返回类型。
- 如果没有定义构造方法,编译器会添加一个无参构造器。如果已定义方法,则不会生成无参构造器!!!
6.构造方法重载
- 传入的参数和类中定义的属性名称一样,使用就近原则,表示的是传入的参数。如果这个时候想要表示类中的属性,需要在前面加this.。
- 构造方法的第一句总是super();
7.垃圾回收机制
有一个”服务员“GC,自动回收垃圾。
- 发现无用的对象。
- 回收无用对象占用的内存空间。
相关算法: - 引用计数法:每个对象有一个引用计数,被引用一次计数加一,被引用变量值变为null,计数减一。直至0变成无用对象。缺点是循环引用的无用对象无法识别。
- 引用可达法:把程序所有的引用关系看成一张图。
8.分代垃圾回收机制
将对象分为三种状态:年轻代,年老代,持久代。虚拟机将内存划为Eden,Survivor和Tenured
- 年轻代
所有新生成的对象首先放在Eden区,目的是尽可能快的手机掉生命周期短的对象,对应的是Minor GC。 - 年老代
在年轻代中经历了N(15)次垃圾回收后仍然存活的对象就会被放到年老代中。当年老代对象过多,就要启动Major GC和Full GC来一次大扫除,全面清理年轻代和年老代的区域。 - 持久代
用于存放静态文件,如Java类,方法等。
垃圾回收过程
- 新建的对象,大部分放在Eden中。
- 当Eden满了,触发垃圾回收机制(GC),将无用对象清理掉,然后剩余对象复制到某个Survivor中(S1),清空Eden区。
- 当Eden再次满了之后,将S1中不能清空的对象放在另一个Survivor中(S2),同时将Eden中不能清空的对象方在S1中保证Eden和S1均被清空。
- 重复多次Survivor中没有被清理的对象,放在年老代(Tenured)中
- 当年老代满了之后,触发一次完整的垃圾回收(Full GC),之前新生地的垃圾回收为(minor GC)
- 使用代码System.gc()可以建议启动GC,但是只是建议没有决定权
容易造成内存泄漏的操作
- 创建大量的无用对象
- 静态集合类的使用:HashMap,Vector等使用最容易造成内存泄漏,他们的生命周期和应用程序一致。
- 各种连接对象(IO流对象,数据库连接对象,网络连接对象)未关闭。
- 监听器的使用
9.对象创建的过程和this的本质
- 分配对象空间,并将对象成员变量初始化为0或空
- 执行属性值的显式初始化
- 执行构造方法
- 返回对象的地址给相关变量
this的本质就是创建好的对象的地址,在构造方法调用前,对象已经创建,所以在构造方法中可以使用this代表当前对象。
this不能用于static方法中,因为this指当前对象而static方法在方法区中。
10.static关键字(静态)
它声明的成员变量为静态成员变量,也称为类变量。类变量的生命周期和类相同, 在整个应用程序执行期间都有效。static修饰的成员变量和方法从属于类,普通变量和方法从属于对象。
11.静态初始化块
构造方法用于对象初始化,静态初始化块用于类的初始化。在静态初始化块中不能直接访问非static成员。
初始化快的执行顺序和继承有关,上溯到Object类,先执行父类的静态初始化块。
static{
这里面不能调用非静态的方法和属性
}
静态快在类加载的时候执行,也就是在构造器之前执行。
12.参数传值机制
JAVA中方法所有参数的传递都是值传递,传递的是值的副本。副本的改变不会影响原参数。
基本数据类型参数的传递
传递的是值的副本,副本改变不会影响原件。
引用类型参数的传值
传递的是对象的地址,因此,副本的改变也会导致原件的改变。
13. java包机制
类似于文件夹的概念。一般是域名倒着写。调用不同包下的文件需要import。
包同名
import java.util.Date
import java.sql.*
java.sql.Date a = new java.sql.Date();
sql和util中都有Date,直接new默认的Date是显示指定的util中的Date,如果想用sql中的Date需要这样写。
静态导入
用于导入指定类的静态属性, import static java.lang.Math.PI
。PI就可以直接使用不需要加Math。
14.继承(extends)
继承是子类对父类的扩展实现了代码的重用,但是父类的东西未必都能用。
- 父类也称作,超类,基类或者派生类。
- Java中只有单继承,只有一个直接父类。不像C++
- 虽然类没有多继承,但是接口可以多继承。
- 子类继承父类可以得到父类全部的属性和方法(除了父类的构造方法),但未必可以直接访问,比如父类的私有属性和方法。
- 定义一个类时没有调用extends,它默认的父类是:java.lang.Object。
- 使用instanceof判断子类的对象是否是父类类型结果是true。反过来是false。
15.重写(override)
子类重写父类的方法,让子类自身的行为替换父亲的行为。
- 方法名,形参列表相同
- 返回值类型要小于等于父类类型(返回值类型可以是父类类型的子类或本身)。
- 访问权限,子类大于父类。
16.Object类
是所有Java类的根基类,所有的Java对象都拥有Object类的属性和方法。
toString方法
我们在打印一个对象时,会默认调用toString方法。
17.equals方法的重写
“==”表示比较双方是否相同,如果是基本类型则表示值相等。如果是引用类型则表示地址相等,即同一个对象。
equals方法用于判断对象的内容是否相等的逻辑。
默认的equals方法就是比较两者的地址是否相同。
18.super方法
是直接父类对象的引用,可以通过super访问父类中被子类覆盖的方法和属性。
构造方法的第一行如果没有显式的调用super(),那么java会默认调用super(),含义是调用父类的构造方法。
继承树追溯
class Solution {
public static void main(String[] args) {
System.out.println("开始创建对象");
Child a = new Child();
}
}
class Father{
public Father(){
System.out.println("父类构造");
}
}
class Child extends Father{
public Child(){
System.out.printf("子类构造");
}
}
这是因为构造方法的第一句总是,super();
属性/方法的查找顺序:
- 查找当前类中有没有属性h。
- 依次上溯每个父类,查看每个父类中是否有h,直到Object。
- 如果没有找到,则出现编译错误。
- 上面步骤,只要找到h变量,则这个过程终止。
19.封装(Encapsulation)
高内聚,低耦合
- 提高代码的安全性
- 提高代码的复用性
- 高内聚:封装细节,便于修改内部代码,提高可维护性
- 低耦合:简化外部调用,易于扩展和协作
访问控制符
- private 表示私有,只有自己可以看见
- default(默认)表示没有修饰符修饰,只有同一个包的类能访问
- protected 表示可以被同一个包的类以及其他包的子类访问
- public 可以被该项目的所有包的所有类访问
使用细节
- 类的属性一般是private访问权限
- 提供相应的get/set方法访问相关变量,这些方法通常用public修饰,提供对属性的赋值和读取操作
- 一些只用于本类的辅助方法可以用private修饰,希望其他类调用的方法用publice修饰。
20.多态(polymorphism)
多态指同一个方法的调用,由于对象不同可能会有不同的行为。同一方法,具体的实现会完全不同。
- 方法多态,属性没有多态
- 多态的存在有三个条件:继承,方法重写和父类引用指向子类对象
- 父类引用指向子类对象后,用该父类引用调用子类重写的方法,此时多态就出现了
21.对象的转型(casting)
- 自动向上转型:可以把子类对象赋值给父类,向上转型后的父类引用变量只能调用它编译的方法,不能调用它运行时类型的方法。(只能调用父类中的方法)
- 强制向下转型:可以把向上转型的对象再转回来。
- 把不同的子类向上转型再向下转型为不同的子类,编译器不会报错但是运行时会报错。
22.final关键字
- 修饰变量:被他修饰的变量不可改变,一旦赋了初值就不能被重新赋值。
- 修饰方法:不能被子类重写,但可以重载。
- 修饰类:不能被继承。
23.数组的使用
数组是相同类型数据的有序集合。描述的是相同类型的若干数据,按照一定的先后次序排序组合。每一个数据称为元素,可以通过索引访问。
- 长度是确定的,大小不可改变
- 元素必须是相同类型
- 数组类型可以是任何数据类型,包括基本类型和引用类型
- 数组变量是引用类型
- 数组就是对象,其中的每个元素相当于该对象的成员变量
- 数组是连续存储的
数组初始化方法
- 静态初始化
int[] a = {1,2,3}; User[] b = {new User(1,"a"),new User(2,"b");}
- 默认初始化:
int[] a = new int a[2];
数组是引用类型,它的元素相当于类的实例变量,因此数组在分配空间后,各个元素也按默认值隐式初始化。 - 动态初始化:在默认初始化之后通过下标赋值。
数组的遍历
for-each循环是专门用于读取数组的
for (int m: a){
System.out.println(m);
}
24.抽象方法和抽象类
抽象方法
使用abstract修饰的方法,没有方法体,只有声明。但是会告诉子类必须给抽象方法提供具体的实现。
抽象类
包含抽象方法的类,必须是抽象类。但是抽象类中可以定义普通方法。抽象类不能实例化,不能new只能被用来继承。
abstract class Solution {
abstract public void a();
public void run(){
System.out.println("run");
}
public static void main(String[] args) {
Solution x = new Abc();
x.run();
x.a();
}
}
class Abc extends Solution{
@Override
public void a() {
System.out.println("aaa");
}
}
25. 接口
为什么需要接口,它和抽象类有什么区别
比抽象类还要抽象的抽象类,可以更加规范的对子类进行约束。全面的实现了规范和具体实现相分离。
接口不提供任何实现,所有方法都是抽象方法。
从接口的实现者角度看,接口定义了可以向外部提供的服务。
从接口的调用者角度看,接口定义了实现者能提供哪些服务。
接口是两个模块之间通信的标准,通信的规范。如果能把你要设计的模块接口定义好,就相当于完成了系统的设计大纲,剩下的就是添砖加瓦的实现了。系统的实现就是面向接口编程的。
接口的父子类不是父子关系,而是实现规则关系。
如何定义和使用接口
- 访问修饰符只能是public或者默认
- 接口名和类名采用相同命名机制
- extends:接口可以多继承(使用extends继承多个接口)
- 常量:接口中的属性只能是常量,总是:public static final修饰,不写默认也是。
- 方法:接口中的方法只能是:public abstract,不写默认也是。
- 子类通过implements实现接口中的规范
- 接口不能声明实例,但是可以用于声明引用变量类型
- 一个类实现了接口,必须实现类中的所有方法,并且这些方法只能是public的
面向接口编程
接口就是规范,是项目中最核心的东西。
26.内部类
成员内部类(非静态内部类,静态内部类),匿名内部类和局部内部类
成员内部类(可以使用private,default,protected,public任意修饰)
a.非静态内部类
- 必须寄存在一个外部类对象里。因此一个内部类对象一定存在一个外部类对象。非静态内部类对象独立于外部类的某个对象。
- 非静态内部类可以直接访问外部类成员,但是外部类不能直接访问非静态内部类成员。
- 非静态内部类不能有静态方法静态属性和静态初始化块。
- 外部类的静态方法,静态代码块不能访问非静态内部类。
class Solution { public static void main(String[] args) { Outer.Inner inner = new Outer().new Inner(); inner.show(); } } class Outer{ private int age = 10; public void testOuter(){ } class Inner { int age = 20; public void show(){ int age = 30; System.out.println("外部类成员变量:" + Outer.this.age); System.out.println("内部类成员变量:" + this.age); System.out.printf("局部变量:" + age); } } }
b.静态内部类
用的不多。
27.String类
- 称作不可变字符序列。
- 位于String.lang包下。
常量池
字符串常量会放在常量池里。
class Solution {
public static void main(String[] args) {
String str = "abc";
String str2 = new String("abc");
String str3 = "abc" + "def";
String str4 = "18" + 19;
String str5 = "abc";
System.out.println(str4);
System.out.println(str == str2);
System.out.println(str == str5);
System.out.println(str.equals(str2));
}
}
常用方法
- str.charAt(x);提取下标为x的字符
- str.equals(str2);比较两个字符串是否相等
- str.equalsIgnoreCase(str2);比较两个字符串是否相等(忽略大小写)
- str.indexOf(“java”)返回字符串第一次出现的位置,如果不存在返回-1
- str.replace(’ ',‘a’)将str中的全部空格用a代替
- str.startsWith(“abc”)判断str是否以abc开头
- str.endsWith(“abc”)同上
- str.substring(4)从下标为4开始到字符串结束
- str.substring(4,7)提取子字符串下标[4,7)不包括7
- str.toLowerCase()全转小写
- str.toUpperCase()全转大写
- str.trim() 去掉首尾空格
28.数组的拷贝
System.arraycopy(s1,2,s2,6,3) 从s1的第二个元素开始拷,拷三个,从s2的下标6开始放。
在数组中删除元素
本质上也是数组的拷贝
29. Arrays工具类
import java.util.Arrays;
class Solution {
public static void main(String[] args) {
int[] a = {10,30,20};
System.out.println(a);
System.out.println(Arrays.toString(a));
Arrays.sort(a);
System.out.println(Arrays.toString(a));
//二分法查找
System.out.println(Arrays.binarySearch(a,30));
System.out.println(Arrays.binarySearch(a,4));
}
}
30. 多维数组
class Solution {
public static void main(String[] args) {
//二维数组每一个元素的长度可以不一样
int[][] a = new int[3][];
a[0] = new int[]{20,30,40};
a[1] = new int[]{1,2};
a[2] = new int[]{3,4};
}
}
31.二分法查找
class Solution {
public static void main(String[] args) {
int[]arr = {30,20,50,10,80,9,7,12,100,40,8};
Arrays.sort(arr);
int low = 0;
int high = arr.length -1;
int value = 10;
while (low<=high){
int mid = (low + high) / 2;
if (value == arr[mid]){
System.out.println(mid);
break;
}
if (value > arr[mid]){
low = mid + 1;
}
if (value < arr[mid]){
high = mid - 1;
}
}
}
}