成长日记(2) Java面向对象
林狗狗狗 人气:0本篇主要是记录自己在学习路上的笔记,如果有哪里记错了请大家直接指出
面向对象的概念
*人为抽象的一种编程模型
*面向过程 代码集中 难以维护
*类:对事物 算法 逻辑 概念等的抽象 理解成 模板 图纸等 相关数据 运算代码 封装成一个类组件
每个对象 占用独立的内存空间 保存各自的属性数据 Solider s1=new Soldier(); 分配内存空间 然后内存地址存到s1 前方加Solider表示可以引用Solider类的内存地址 如果想引用别的对象 则可以使用 Object s1 = new Solider() 这样就引用不到Solider()的内容了
可以独自控制一个对象 来执行指定的代码
*引用:保存对象的内存的地址 理解成一个遥控器
引用变量的特殊值:null
*不保存任何对象的地址
*构造方法 是新建对象时执行的一个特殊方法(只能在新建对象的时候执行)
*构造方法重载
*如有多个 构造方法 可以使用 public Student(int id,String name,String gender ){
this(id,name,gender,0)
} 防止多次修改
*this
两种方法: 必须首行
this.xxx 调用成员 当有重名局部变量 必须用this.xx调用成员 引用当前对象的地址
this() 构造方法之间调用 目的是减少代码重复 方便维护修改 一般是参数少的方法调用参数多的方法
*方法重载 Overload
同名不同参 void f(){} void f(int i ){} 如果同名同参而返回类型不一样,也会报错 int f(double d){} 也是重载
是否重载只看 同名不同参 别的都无所谓
存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。应该注意的是,返回值不同,其它都相同不算是重载。
*二进制
二进制前缀:0b 如:0b1001 如果想写二进制 可以在 前面加0b直接写
*计算机表示任何数据 都是二进制数字
*电容高电位,1
*电容低点位,0
*一个电位,称为bit(比特)
*一个字节 8个电位
*二进制转十进制 Integer.parseInt("1011101",2) 后面加2表示二进制
*十进制转二进制 Integer.toBinaryString(255);
*byte类型
byte类转int类 直接变成int
int转byte 当值大于128 先变成二进制 把大于8位的去掉 如果第八位是1 则前七位取相反的数 把前七位的数算出来变成加1变成负数就行
如果第八位是0 直接算出前7位的和
*继承 作用:代码重用 代码复用
必须是单继承关系 一个子类只能继承一个父类 一个父类可以有多个子类
不继承的东西:构造方法
如果子类的方法重写了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。这是为了确保可以使 用父类实例的地方都可以使用子类实例,也就是确保满足里氏替换原则。
子类继承父类的之后 调用方法是先找子类中的方法 找不到去找父类的方法
调用成员时, 先找子类 再找父类 如果父类有构造方法 当创建子类对象时 会先调用父类构造方法 默认执行父类的无参构造
子类对象的super() 必须在首行执行 并且不能跟this一起用
*重写 Override
子类中 重新定义、重新编写从父类继承的方法
调用该方法适时 先在子类中找到这个方法执行 找不到再去父类找
super.xxx() 重写方法时 调用父类中同一个方法的代码
重写的限制:
子类方法的访问权限必须大于等于父类方法;
子类方法的返回类型必须是父类方法返回类型或为其子类型。
子类方法抛出的异常类型必须是父类抛出异常类型或为其子类型。
使用 @Override 注解,可以让编译器帮忙检查是否满足上面的三个限制条件。
*多态:作用:一致的类型 类型转换:向上转型 子类转型成父类 当做父类型 其实本质上还是子类 向下转型 已经转成为父类型的子类对象 转回成子类
所有子类型对象,都可以被当做一致的父类型来处理
多态的好处: 各种子类型都可以当做父类型一起处理 可以减小代码量
*instanceOf :运行期类型识别
s instanceof Line 判断s是不是Line类型 返回true或false 判断父类也是true
*向上转型: 子类继承父类 可以用 父类 b = new 子类() 自动转型 b.方法 执行的是子类发出的(父类和子类共有的)方法 相当于爸爸用儿子的东西 就是爸爸使用自己有的 儿子也有的 儿子的方法
*向下转型: 如果有3个类别实现了 一个接口 然后想将3个类别放在一个数组列表里 那么只能使用一个List<电子设别>的列表装载 但是用了这个以后相当于是向上转型了 则会丢失3个类各自的方法 于是在取出来的时候在前方 重新标识 (手机)电子设备.get(手机) 完成向下转型 成功拥有手机的方法 ,这是个人对向下转型的理解 说明向下转型不能独自使用 必须向上转型后 再向下转型
*String : 在java8中 本质上是char[]数组的封装的对象 在java9中 String类的实现改用byte数组存储字符串 同时使用coder来标识使用了哪种编码
*String被声明为final 因此他不可继承 由于String中的value数组声明为final 所以他初始化后不能引用其他数组 并且内部没有改变数组的方法 因此可以保证String不可变
String不可变的好处:1.因为String的hash值经常被使用 例如String用作HashMap的key 不可变的特性可以使得hash值也不可变,因此只用一次计算 只要字符串相同 返回的hash值也相同
2.如果一个String对象已经被创建过了,那么就会从String Pool中取得引用,只有String是不可变的 才能使用StringPool
StringPool(如果是 String a = 123 字符串在创建出来后 就被存入StringPool 中 如果是 String a = new String(a) 则是在堆中 StringPool在 堆中 因为原来的空间不足)
3.安全性:String的不可变性可以保证参数不可变
4.线程安全:String不可变性天生具有线程安全,可以在多个线程安全的使用
*字符串.intern() 方法 会先去找StringPool中的一样的地址 取出StringPool中地址一样的值 如果是直接new的话 则是默认堆中
*charAt(i) 取指定位置的字符
*indexOf(子串) 查找子串的位置 没有返回-1
*substring(from) 截取from到末尾
*substring(from,end) 截取from到end
*String对象的标准创建方式 char[] a = {'a','b','c'} String s= new String(a)
* String abc = new String("abc") 这样new的方式会创建两个字符串对象(前提是StringPool中没有这个对象) 因为编译的时候会在 String Pool 中创建一个字符串对象指向这个 "abc" 字符串字面 量; 而使用
new 的方式会在堆中创建一个字符串对象。
*字符串的字面值 "abcd" 第一次用到一个字面值时,在"字符串常量池"中新建对象 然后封装一个char[]数组 'a','b','c','d'
*再次用到相同的字面值时,直接访问"常量池"中已经存在的对象,而不是重新创建
*String s1 = new String("a") 这是新分配内存 与数组无异
*String s2 = "abcd" 这是常量池新建对象
字符串不可变 保证常量池的效率
字符串加号连接,会新建字符串,效率低下
*补充: System.currentTimeMillis() 获取系统当前的毫秒值 从1970-1-1 0点开始的毫秒值
*StringBuilder
*StringBuilder : 如果要对字符串进行多次连接 那么使用StringBuilder StringBuilder也是线程不安全的
*可变的字符序列
*封装char[] 数组
*提供了一组方法,可以对内部字符进行修改
*常用来代替String,做高效率的字符串连接运算
*append() 方法
StringBuilder 内部数组默认初始长度16
当数组放满了以后 创建新的容量翻倍的数组并把原来的内容放进去
*创建StringBuilder StringBuilder sb = new StringBuilder(); 这样就创建了一个初始大小为16的数组 然后使用sb.append("") 添加
*效率非常高 *******
*setCharAt(i,字符) 将字符放在i处
*delete(from,end) 删除从from - end 的字符
*<>泛型: 泛型是一种类型参数 不支持基本类型
*正则表达式 Regex
*正确的字符串格式规则
* 一般用来判断用户输入的字符串格式 是否符合格式要求
*[abc] 可匹配a b c中的一个 [a-z] 匹配a-z中的一个 [a-zA-Z0-9] 大小写0-9都可以 [\u4e00-\u9fa5] 中文范围 \d 排除数字 \w排除单词a-zA-Z0-9
\s 空白字符 \S 排除空白字符 .任意字符 ? 0到1个 * 0到多个 + 1到多个 {3} 固定3个 {3,5} 3到5个 {3,} 3到多个 | 或 ^在字符集内表示排除
^ 字符集外 表示起始位置 ^a\d+ 以a开头后面跟多个数字 $ 表示结尾位置 \d+a$ 表示数字开头 a结尾
*字符串的正则表达式匹配运算方法
*matches(正则) 判定 s.matches(正则)
*split(正则) 拆分
*replaceAll(正则,字符串)
*Integer
integer a = 123 与 Integer.valueof(123) 是一样的
Integer类中,有一个Integer对象缓存数组 其中缓存了256个对象 封装值的范围是[-128,127] 就是缓存池
指定范围内的值 直接引用 范围外的值 新建
Integer重写了equal()方法 比的是内部封装的值
如果在-128到127之间 访问缓存池下标值 取出来地址
*接口:接口就是极端的抽象类 里面没有完成的方法 不能有任何地方的实现
从 Java 8 开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口的维护成本太高了。在 Java 8 之 前,如果一个接口想要添加新的方法,那么要修改所有实现了该接口的类。
*在 Java 中,类是单继承的,但是却可以继承多个接口,而接口可以继承多个接口。
作用:结构设计工具 用来解耦合 隔离实现
用Interface 代替class
用implements 代替extends
接口中 只能定义: 提高代码复用性
公开的常量 public static final int type= 461; 写的时候可以省略 public static final
公开的抽象方法 因此可以省略 public abstract xxx xxx()
公开的内部类 内部接口
不能定义构造方法
不能定义变量
*抽象类比较接口:抽象类是继承关系 即子类对象必须能够替换掉所有父类对象 而接口是提供一种方法实现契约
接口的字段只能是static 和 final类型的 而抽象类的字段没有这种设置。
接口的成员只能是public 而接口类的成员可以有多种访问权限
*和抽象类的使用选择:
使用接口:需要让不相关的类都实现一个方法,
需要多重继承。
使用抽象类:
需要在几个相关的类中共享代码
需要能控制继承来的成员的访问权限 而不是都是public
需要继承非静态和非常量字段
在很多情况下 接口优先于抽象类,因为接口没有抽象类严格的类层次结构要求 ,而且从java8开始 接口也可以有默认的实现方式 使得修改接口的成本变得很低
*Object通用方法:
*equals() 1.等价关系 自反性:x.equals(x) true 对称性 x.equals(y) == y.equals(x) true 传递性:if(x.equals(y) && y.equals(z)) x.equals(z) true
一致性:x.equals(y) == x.equals(y) true 与null的比较 x.equals(null) false
2.对于基本数据类型 ==判断两个值是否相等 基本数据类型 没有equals方法 对于引用数据类型 == 判断是否引用一个对象 equals()判断值
3.equals原理:首先判断是否为一个对象的引用 如果是 直接返回true 之后判断是否是一个类型 如果不是 直接返回false 之后将Object对象进行转型 判断每个关键域是否相等
*hashCode() 返回散列值,而equals()是用来判断两个对象是否相等。等价的两个对象散列值一定相等, 但是散列值相同的两个对象不一定等价。
有些方法是通过hashCode()判定是否相等 所以如果equals成立则也要保证等价的两个对象散列值也相等。
*hashCode()和equals(): 作用其实一样 都是用来对比两个对象是否相等一致 为了维护hashCode()方法的equals协定,在改写equals()时 总要修改hashCode() 举例:HashMap对象是根据其key的hashCode来获取对应的value 当此对象做Map类的key时,两个equals为true的对象其获取的value是同一个 不然 一个相同的对象可能会获取不同的对象
区别:equals()比较的比较全面也比较复杂,这样效率比较低。而利用hashCode()进行对比 则只要生产一个hash值就可以比较 效率高,equals相等的时候 hashCode肯定相等 也就是用equals()是绝对可靠的
在做大范围比对的时候,用equals太慢 可以先用hashCode判定 如果不一样则直接不一样 如果一样再用equals判定 提高效率
*toString 默认返回ToStringExample@4554617c这种形式 其中@后面的数值为散列码的无符号十六进制表示。
*clone() 克隆 clone() 是Object的protected方法 他不是public 一个类不显示去重写clone() 其他类就不能直接去调用该类实例的clone()方法
要使用Object 的 clone() 方法的时候 需要实现Cloneable接口,因为Cloneable接口规定 如果一个类没有实现Cloneable接口又调用了clone()方法,就会抛出CloneNotSupportedException 所以clone还是Object的方法 但是必须要实现接口
示例: public Object clone() throws CloneNotSupportedException{ return super.clone() } 需要实现接口并重写clone
疑问 如果所有类都是clone类的子类 那么为什么不能直接继承protected 还是对于Object来说 别的类都是他的外部类
*浅拷贝:拷贝对象和原始对象的引用类型引用同一个对象 克隆前的对象改变了 克隆后的对象也会改变
**深拷贝:拷贝对象和原始对象的引用类型引用不同的对象 深拷贝需要自己再写一些代码 (还待考量 后期弥补)
对于数组:
@Override
protected DeepCloneExample clone() throws CloneNotSupportedException {
DeepCloneExample result = (DeepCloneExample) super.clone();
result.arr = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
result.arr[i] = arr[i];
}
return result;
*clone的替代方案
*使用clone()方法来拷贝一个对象既复杂又有风险,它会抛出异常 还需要类型转换,Effective Java书上讲到,最好不要去使用clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。
如下用构造方法拷贝:
public CloneConstructorExample(CloneConstructorExample original) {
arr = new int[original.arr.length];
for (int i = 0; i < original.arr.length; i++) {
arr[i] = original.arr[i];
} }
*关键字
*final
*1.声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能改变的常量。
*对于基本变量,如果在初始化以后 final使数据值不变。 如 final int x = 1; x=2 会报错
**对于引用类型,final使引用不变,也就是不能引用其他对象 但是被引用的对象本身是可以修改的 如 final A a = new A(); y.a = 1
*2.方法 声明方法不能被子类重写 。
private方法隐式的背指定为final 如果在子类中定义的方法和基类中的private方法签名相同 此时子类的方法不是重写基类方法 而是在子类中新定义了一个方法
*3.类 声明类不允许被继承
*static
*静态变量:又称为类变量,也就是说这个变量是属于类的,类的所有实例都共享静态变量,可以直接通过类名来访问它。静态变量内存中只存在一份。
举例:A类中如果定义了static z变量,可以在main方法中直接 A.z 而没有static修饰的变量则会报错
*实例变量:没创建一个实例就会产生一个实例变量,它与该类同生共死
举例:有private 修饰的变量 在本类中 实例化后可以直接访问,但是在别的类中则不能直接访问
*静态方法:静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说不能是抽象方法。 static不能和abstract一起
举例:public abstract static void func2(); 会报错。
而且静态方法只能访问属于类的静态字段和静态方法 方法中不能有this和super
*静态语句块: 静态语句块在类初始化时就运行一次
举例:static{system.out.println("123")} 初始化运行一次
**静态内部类:非静态内部类依赖于外部类的实例,而静态内部类不需要。
内部静态类不能访问外部类的非静态的变量和方法
*静态导包:在使用静态变量和方法时不用再指明ClassName,从而简化代码 但可读性大大降低
举例: import static com.xxx.className
*静态初始化顺序:静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序
举例 :先 public static String staticField ="静态" 然后 static{syso} 然后 才是普通的public String field="实例" 和{} 最后才是构造方法
前两者static的顺序取决于位置
*在存在继承的情况下 , 初始化顺序为
父类(静态变量、静态语句块)
子类(静态变量、静态语句块)
父类(实例变量、普通语句块)
父类(构造函数)
子类(实例变量、普通语句块)
子类(构造函数)
*反射
*java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能成为反射机制
*使用"类对象"来执行反射操作 反射是所有注解实现的原理
*获得类对象(三种方式) 如果类还没加载 则先去加载
1.A.class 2.Class.forName("day1901.A") 3.a1.getClass()
Class<?>c =Class.forName("java.lang.String"); 获取到后可以
如果知道类名的话 : 比如要反射Student类
Class studentClass = Class.forName("com.test.reflection.Stutent") 第一种 用全类名class对象
Class studentClass2 = Student.class; 第二种是 通过类名的class 但是嗷导入类的包
Student student = new Student() Class studentClass3 = student.getClass(); 第三种已经有了Student 不需要反射
*获取成员变量: getDeclaredFields getFields
Field[] declearedFieldList = studentClass.getDeclaredFields(); 用于获取所有声明的字段(变量) 包括公有字段和私有
Field[] fieldList = studentClass.getFields(); 仅用来获取公有字段(变量)
*获取构造方法: getDeclaredConstructors getConstructors
Constructor[] declaredConstructorList = studentClass.getDeclaredConstructors(); 获取所有构造方法 私有和公有都获取
如果是要使用私有的构造方法 则还要加上:studentConstructor.setAccessible(true); 公有的就不用加
Constructor[] ConstructorList = studentClass.getConstructors(); 获取公有构造方法
*获取其他非构造方法: getDeclaredMethods 和 getMethods
Method[] declaredMethodList = studentClass.getDeclaredMethods(); 获取所有非构造方法 不论公有还是私有 无法获取到父类
Method[] MethodList = studentClass.getMethods(); 获取公有的非构造方法 可以获取到父类(Object )
举例:
Class personClass = Class.forName("practice.Person");
Constructor constructor = personClass.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
Object stu = constructor.newInstance("lin");
Field studentField = personClass.getDeclaredField("age");
studentField.set(stu, 10);
Method declaredMethod = personClass.getDeclaredMethod("show");
declaredMethod.setAccessible(true);
Object result = declaredMethod.invoke(stu);
System.out.println(result);
*Filter:可以使用get()和set() 方法读取和修改Field对象关联的字段
*Constructor:可以使用constructor.newInstance()使用构造方法创建对象
*Method 可以使用method.invoke(stu) 可以调用方法 stu为之前创建的对象
*如果要使用private类 或者对象 需要 对象.setAccessible(true);
String pachageName = c.getPackage().getName(); //获得包名
String name = c.getName(); //获得类名
String simpleName=c.getSimpleName(); //获得简写类名
*反射的优点:
1.可扩展性:应用程序可以利用全限定名创建可扩展对象的实例,来使用来自外部的用户自定义类 (可以用反射用外部的方法)
2.类浏览器和可视化开发环境:一个类浏览器可以枚举类的成员。可视化开发环境可以从利用反射中可用的类型信息受益,可以更好的编写代码
3.调试器和测试工具:调试器需要能够检查一个类里面的私有成员 测试工具可以利用反射来自动调用类里定义的可被发现的API定义,以确保一组测试中有较高的代码覆盖率
*反射的缺点:
1.性能开销:反射涉及了动态类型的解析,所有JVM无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多,应该避免经常执行的代码使用反射
2.安全限制:反射技术要求程序必须在一个没有安全限制的环境中执行。如果程序必须在有安全限制的环境中执行 那么就会出问题
3.内部暴露:由于反射允许代码访问私有的属性和方法 所以使用反射可能会有意料之外的副作用
*异常
Throwable(异常机制) 可以用来标识任何作为异常抛出的类,分为两种:Error 和 Exception。其中Error 用来表示JVM无法处理的错误,Exception分为两种: 1.受检异常:需要用try..catch...语句捕获并进行处理,并且可以从异常中恢复; 2.非受检异常:是程序运行时错误 例如除0会引发Arithmetic Exception 此时程序崩溃并且无法恢复。
*throws: 设置异常的抛出管道
*throw 手动抛出异常
*RuntimeException 默认抛出管道 非检查异常 -编译器不检查
*其他异常: 强制处理 二选一 catch throws 检查异常 -编译器检查 不处理不能编译
*异常包装
*捕获的异常 包装成另一种类型 再抛出
try{} catch(A Exception e ){ throw new BException(e) }
*使用场景:不能抛的异常类型 包装成能抛出的异常再抛出
*泛型
*泛型有3种使用方式 泛型类、泛型接口、泛型方法
*泛型类:泛型类型用于类的定义中,被称为泛型类,通过泛型可以完成一组类的操作对外开放相同的接口,最典型的就是各种容器类如:List Set Map
泛型的T 可以随便换 常见如 T K E V等形式
*泛型接口 :泛型接口与泛型类的定义及使用基本相同
*泛型通配符:类型通配符一般是使用?代替具体的类型实参,注意了,此处’?’是类型实参,而不是类型形参 。
*泛型方法:泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型 。
public class Box<T> {
// T stands for "Type"
private T t;
public void set(T t) { this.t = t; }
public T get() { return t; } }
JDK和JRE
*JRE是JVM程序 java应用程序需要在JRE上运行 就是运行时环境
*JDK是java开发工具包 。JDK包含了JRE 同时还包含了编译java源码的编译器javac
加载全部内容