面向对象2
基本类型的封装类型
概述
所有基本类型都有相对应的封装类型,类名一般与基本类型一致但首字母大写,大部分情况下,基本类型可以执行的操作,封装类型都可以完成
java提供的封装类型,可以调用已经封装好的属性和方法,相比基本类型功能会更多
基本类型 | 封装类型 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
基本类型与封装类型之间的转换
对应的基本与封装类型可以直接用=进行转换,程序在执行时编译器会自动为基本类型进行装箱操作,为封装类型执行拆箱操作
//封箱
//Integer integer = Integer.valueOf(10/"10");
Integer integer = 10;
//拆箱
//int i = integer.intValue();
int i = integer;
-
其他封装类型基本都可以通过toString转换成String类型
如:String s = new Double(10.1).toString();
-
封装类型转其他基本类型,需要用到对应的xxxValue()方法(基于封箱操作,也可以用封装类型来接收结果)
如:double d = Integer.valueOf(10).doubleValue();
-
封装类型之间的转换也遵循基本类型的“小转大,直接转”原则
Integer类中存在一个缓存区(-128~127),以Integer i = 数值 形式创建的Integer类型对象,只要数值的大小在范围内,它们的地址都相同
重写&重载
重写
重写是只存在于父子继承关系中的一种方法多态的表现;当父类中的方法无法满足子类需要时,子类可以创建一个和该方法返回值、方法名、参数列表一模一样的方法,以达到覆盖父类方法的目的,该过程被称为重写
工具中自动生成的重写方法中,会带有一个@Override标记,这会在父类中强制检查当前方法是否是合法重写,super()可以按需自行去除
除了以上所说的返回值、方法名、参数列表必须和被重写方法完全一致外,子类中重写方法的修饰符可以大于或等于父类
代码示例:
class Father{
protected void m(){
System.out.println("father");
}
}
class Son{
@Override
public void m(){
System.out.println("son");
}
}
当所有子类都一定会对父类方法进行重写时,可以把这个方法用abstrac修饰,定义为抽象方法,详情↓
toString & equals的重写
java中所有类的顶级父类是Object,Object中包含两个方法toString和equals,当直接输出对象名时会调用toString输出对象地址,当调用equals时会比较两个对象的地址是否相同,但在实际项目中,经常会对这两个方法进行重写
-
toString
重写目标:输出类中的属性信息
-
equals & hashCode
重写目标:比较两个对象内容,为了保持逻辑和实际上的地址一致,重写equals后也要重写hashCode();
大致重写方向:
-
判断比较对象是否和当前类的类型一致,如果是则将比较对象转换成当前实例的类型
if(!(obj instanceof User)){
return false;
}User u = (User)obj; -
再依次比较对象中每个属性的值是否一致,如果全部相同,则返回true
instanceof关键字(运算符):用于判断两个对象的类型是否一致 ,结果为true/false
-
重载
只要和某一个方法的方法名相同,但参数列表不同(个数、类型、位置任意一项不同),就构成方法重载,一般存在于同一个类中。
为什么方法重载与返回值无关?
在调用方法时不一定要接收返回值,直接使用的话,即使返回值不同,如果使用相同的参数列表,编译器依旧不知道要使用哪一个方法,所以方法重载的要求不包括返回值
重写和重载的区别
- 重写必须存在继承关系,由子类去重写父类方法,重载无要求
- 重写的方法名称返回值参数类型必须和父类一致,重载的参数个数/顺序/类型至少有一项不一致
- 重载与返回值无关
Static & final
static关键字(静态)
可以修饰方法、属性和内部类,被static修饰的元素在虚拟机启动时就已经加载,与类无关,直到虚拟机关闭后,这些元素才会被回收。
- 修饰方法时,表示为静态方法,无需创建对象就可直接调用,在其他类中通过类名直接调用
- 修饰属性时与上一条基本一致
- 修饰游离块时,被称为静态游离块
游离块和静态游离块
游离块是一种特殊的语句块,一般用作初始化操作,所以又被称为初始化块,一般是将多个构造器都要执行的重复代码都放在游离块中书写,每次创建对象时它都先于构造方法执行
静态游离块是在游离块的基础上加上static修饰符,static的性质使它与类无关,仅仅在虚拟机启动时执行一次。
创建对象时,执行顺序是
- (虚拟机启动时执行一次)静态游离块->(每次new对象时){游离块->构造器}
class Test{ static{ System.out.println("第一个执行"); } { System.out.println("第二个执行"); } public Test(){ System.out.println("第三个执行"); } }
final关键字
可以修饰类、属性和方法,表示最终的
- 修饰类时,表示该类无法拥有子类(因此一定需要子类实现的接口不能用final修饰)
- 修饰方法时,表示该方法无法被重写
- 修饰属性时,表示该属性无法被更改(一般会与static组合),即常量
abstract & interface
抽象类
抽象类中才能包含抽象方法,但也可以包含其他普通方法和属性
特点:
-
抽象类必须使用abstract修饰,抽象方法必须存在于抽象类中
-
抽象类存在构造器,但无法实例化
-
抽象类通常会包含抽象方法
-
抽象类的存在一般是需要有子类去继承的
- 子类中必须要实现父类中的抽象方法(空实现也行),除非子类也是抽象类
- 抽象类允许继承其他抽象类,但只能单继承,不过可以用多重继承的方式实现多继承
-
构造器和属性无法被abstract修饰
接口
接口中只能包含常量和抽象方法(JDK 1.8之前),接口不存在构造器,因此无法被实例化,一个类允许一次实现多个接口,但接口不能继承其他类,也不能实现接口,但可以继承多个接口
- 在接口中以变量形式声明元素,编译器会自动为该元素加上public static final,使其变成常量
- 接口中也会自动为方法加上public abstract,使其成为抽象方法
抽象类和接口的区别
- 抽象类是对类的抽象(类似名词),接口是对行为的抽象(类型动词)
- 抽象类通过extend继承扩展,接口通过implement实现扩展
- 子类只能继承一个抽象类,但可以继承多个接口
- 抽象类能且只能继承一个父类,接口可以继承多个接口,但接口不能继承任何类
内部类
被public修饰的类内部存在的类被称为内部类
成员内部类
在类中定义,与成员方法、成员属性、构造器平级,成员内部类类似成员方法,允许被任何的访问修饰符修饰(包括private和default)
局部(临时)内部类
在一个类的方法或其他成员内部类、游离块中创建的类被称作局部内部类,与临时变量类似,不能用修饰符修饰(只能用于方法内部,修饰符无意义),局部内部类可以使用所在方法中的变量,但无法对其进行修改,如果需要使用外部变量还需要对该变量用final进行修饰。
使用final修饰的原因是因为,如果方法使用完毕,但内部的类可能还在运行,还在使用方法里的变量,为了避免变量因为方法的结束而被清理,就需要添加final延长局部变量的生命周期。
静态内部类
即用static修饰的成员内部类,用static修饰后,该内部类与当前类无关,一般用于数据缓存,如Integer中的静态内部类IntegerCache。
public class StaticClass {
public void f() {
System.out.println("内部类方法");
}
public static class MyCache{//静态内部类 = 成员内部类+static
static String msgString = "";
public static void m() {
System.out.println("静态内部类的静态方法");
}
public static void m2() {
System.out.println("静态内部类的普通方法");
}
}
public static void main(String[] args) {
//由于myChache是静态内部类,所以不需要用外部类对象去创造内部类
StaticClass.MyCache chache = new MyCache();
//调用静态内部类-静态方法-直接通过类名调用
StaticClass.MyCache.m();
//调用静态内部类-普通方法-需要对象
chache.m2();
}
}
匿名内部类
相当于创建一个没有名字的子类去继承、实现普通类/抽象类/接口,一般用作事件监听,也可以使用这种方法去实例化抽象类和接口,由于匿名内部类创建后就已经处于继承父类和实现了一个默认接口的状态,所以无法再显示实现接口和继承其他父类。
多态
方法多态
方法重写和方法重载
属性多态
- 父类引用指向子类对象
- 子类引用指向父类对象?(前提是父类本身就是指向该子类)
应用:对象的强制转换
Animal animal = new Panda(); //Animal是Panda的父类,eat是Panda实现的接口Active方法 public void eat(Object object){ //下转型(下塑造型) Panda panda = (panda)object; System.out.println(panda.getName()+"吃竹子"); } public static void main(String[] args){ Panda panda = (Panda) animal;//子类指向父类对象 panda.eat();//此时才能调用子类独有的方法 }
多态也被称为动态绑定: 运行期间动态为引用变量绑定对象
语法糖技术(动态数组)
之前提到过的语法糖技术还有foreach long类型可以用下划线隔开等
/**
* 动态数组参数(语法糖技术):
* 允许在定义方法时,将参数定义为动态数组参数
* 在调用时可以动态传入任意多个符合要求的数据类型值
* 动态数组必须放在参数列表的末尾
* @author z
*/
public class zeroToMore {
public void showAll(int num,String...names ) {
// public void showAll(String...names ) {
for (String s : names) {
System.out.println(s);
}
}
public static void main(String[] args) {
zeroToMore test = new zeroToMore();
test.showAll(1100,"张三","李四","王五","赵谦","孙武");
}
}
常用类
String
定长字符串,一旦创建就无法改变,所以字符串的相加,其实是创建了新的字符串对象,所以在进行大量的字符串相加操作时不会使用String,一般会使用以下两种类型(StringBuffer & StringBuilder)
常见构造器(5/13)
构造器 | 说明 |
---|---|
String(“123”) | 根据提供的字符串直接创建String对象 |
String(char[] arr) | 根据提供的字符数组直接创建String对象 |
String(char[] arr,int start,int lenght) | 根据提供的字符数组+偏移量(开始读取的位置和读取长度)直接创建String对象 |
String(byte[] arr) | 根据提供字节数组直接创建String对象 |
String(byte[] arr,int start,int lenght) | 根据提供的字节数组+偏移量(开始读取的位置和读取长度)直接创建String对象 |
常用方法
- lenght()-获取字符串的字符个数
- charAt()-获取指定位置的字符
- indexOf()-获取指定字串/字符/数字第一次出现在目标字符串中的索引
- compareTo()-比较两个字符串在字典(ASCII)的顺序(逐字比较,如果有不同就不比较下一个字符)
- compareToIgnoreCase()-按字典顺序比较两个字符串,忽略病例差异
- concat()-将指定的字符串连接到该字符串的末尾
- contains()-判断目标字符串是否包含指定(连续的)子串
- startsWith()/endsWith()-判断目标字符串开头/结尾是否是指定字符串
- equalsIgnoreCase()-忽略大小写比较
- isEmpty()-判断字符串是否为空(空格是false),源码中其实是判断字符串长度是否为0
- join(参数个数为0~∞)-将(字符串类型的接口类型CharSequence)参数用"-"拼接成一个完整的字符串
- lastIndex()-返回指定字符串最后一次出现的索引
- substring()-从目标字符串的指定索引开始返回一个新字符串
StringBuffer & StringBuilder
两者都是可变字符串,即使进行多次的字符串修改操作,它们的地址都不会改变
主要方法
两者的构造方法和api几乎一致,以下方法的返回值都是自身,所以可以在后无限调用方法和属性(链式编程)
- append:末尾追加字符串
- delete:删除字符串
- insert:插入字符串
主要区别
- StringBuffer是线程安全的,在多线程条件下,速度会比StringBuilder稍低,但保证了数据安全
- StringBuilder是不安全的,但速度会更快
Arrays
Arrays类有一种静态方法可以把数组转换成List类型(而List是ArrayList的父类)
Math
包含基本数学运算方法,如基本指数,对数,平方根和三角函数
RunTime
与系统环境交互相关的类
设计模式——单例模式
单例模式概述
单例模式也称为单态/单子,指程序运行过程中始终只存在一个对象,单例模式的表现形式又分为:
- 懒汉式: 需要时再创建,以时间换空间的概念
- 饿汉式: 类加载即创建,以空间换时间的概念
创建单例模式要点
- 构造器私有化
- 通过静态方法去返回创建的对象(所有返回的都是同一个实例)
懒汉式
public class Singleton {
//创建一个引用
private static Singleton instance;
//保证构造器私有化,这样就可以禁止外界随意创造对象
private Singleton() {};
public static Singleton newInstance() {
if (instance == null) {
//当第一次创建对象时,引用为空,创建实例
instance = new Singleton();
}
//否则返回原有对象
return instance;
}
}
饿汉式
//类加载时,实例就已经创建
private static Singleton2 instance = new Singleton2();
private Singleton2() {
}
public static Singleton2 newInstance() {
return instance;
}
public static void main(String[] args) {
Singleton s1 = Singleton.newInstance();
Singleton s2 = Singleton.newInstance();
Singleton s3 = Singleton.newInstance();
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
}
单例的应用场景
-
资源池
- 数据库的连接池
- 字符串常量池
- 线程池
-
与系统环境交互相关的类
-
日历
-
系统信息
RunTime类
-
设计模式——模板方法
模板方法是把某些方法实现,另一些方法定义抽象,在实现方法中调用未实现的方法,为实现方法的具体实现靠子类完成
例如:有银行账户类,活期账户和定期账户的存款利率各不相同
抽象类
public abstract class Account {
//本金
private double money;
//利息
private double interest;
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public double getInterest() {
return money * getInterestRate();
}
public void setInterest(double interest) {
this.interest = interest;
}
/**
* 返回存款利率
* @param interest
* @return
*/
public abstract double getInterestRate();
}
子类
/**
* 活期账户
* @author z
*/
public class ActiveAccount extends Account{
//总的存款月数
private int month;
//基础利率
private double base;
public ActiveAccount(int month , double base) {
super();
this.month = month;
this.base = base;
}
@Override
public double getInterestRate() {
return month*base;
}
}
/**
* 定期账户
* @author z
*/
public class FixedAccount extends Account{
private int year;
private double base;
public FixedAccount(int year, double base) {
super();
this.year = year;
this.base = base;
}
@Override
public double getInterestRate() {
// TODO Auto-generated method stub
return year * base *2;
}
}