面向对象的核心—类和对象
类的语法定义
类的修饰符
static可以修饰变量和方法,称为类变量、类方法,它们属于类本身。不被static修饰的变量和方法称为实例变量、实例方法,属于类的实例。
在类准备阶段,系统就为类变量分配内存空间,类初始化阶段完成类变量的初始化。类变量可以通过类来访问,也可以通过类的对象来访问(其实系统在底层转换成通过该类访问类变量,并不会为类变量分配内存),类方法也是。
类成员的作用域比实例成员的作用域更大,可能类成员初始化完成而实例成员未初始化完成,为避免错误,类成员不能访问实例成员。
[public/final/abstract] class Name{
//构造器
[public/private/protected] Name(struct val){
}
//成员变量
[public/protected/private/static/final] type tname [= defualt];
//方法
[public/protected/private/static/final/abstract] type set/get(struct val){
}
}
- 类名
- 成员变量
类的成员变量分为类变量和实例变量,类变量与类共存亡,实例变量与对象共存亡。
- 局部变量
局部变量分为形参、方法局部变量和代码块局部变量,除了形参之外的局部变量声明后必须显式初始化才能使用(系统不会默认初始化局部变量)。局部变量保存基本类型的值或者对象的引用,存放在栈内存中,随着方法或代码块的结束而消亡。
- 构造器
- 构造器名与类名相同,不能显式指定返回值(隐式返回当前类)。没有显式提供构造器时调用系统默认构造器,即Java至少有一个构造器。
- 创建对象的根本途径就是使用new关键字调用类的构造器。
`Person p = new Person();`
p是一个引用变量,只存储一个地址值,Java程序不允许直接访问堆内存中的对象,只能通过对象的引用来访问。`创建对象`的过程如下:
- 调用构造器
- 类准备阶段--系统为对象分配内存空间
- 类初始化阶段--执行初始化块,执行默认初始化
- 通过this引用执行构造器内的程序体,进行显式初始化
- 返回该对象
- 构造器重载
如果一个构造器包含另一个构造器,则可以使用this来调用另一个构造器,方便以后的修改,降低维护成本。
- 初始化块
如果多个构造器中有相同的初始化代码,则可以把它们放到普通初始化块里完成。初始化块总在构造器执行之前执行。static{}静态初始化块初始化类。
- 方法
- Java的方法只能属于类本身或者类的对象,不能独立存在。
- Java方法只能使用值传递。如果参数是基本类型的值,则会开辟新的栈内存存放临时变量;如果参数是引用类型的值,也会开辟新的栈内存存放引用变量,但是变量都是指向堆内存的同一对象。
- 参数为数组时可以采用形参个数可变的方法,进而使用foreach循环。
`public void exmple(int a, String... strings){}`
- 方法重载
同一个类相同方法名,参数列表不同即为重载,和其他项无关。
类的作用
定义变量
创建类的对象
调用类的方法或访问类的变量
this引用
this关键字总是指向调用该方法的对象。
构造器中引用正在初始化的对象
public Person(int age){
this.age = age;
}
- 普通方法中引用调用该方法的对象(可省略this关键字)
谁调用set()方法,this代表谁。
public void get(){
}
pubilc void set(){
this.get();
}
- static修饰的方法不能使用this引用,因为如果使用this,则this无法指向合适的对象
抽象类
抽象类作为一种模板,提供通用的方法,是一种更高层次的抽象。
抽象方法只有方法签名,没有方法实现。有抽象方法的类只能被定义为抽象类,而抽象类里可以没有抽象方法。抽象类和抽象方法都用abstract修饰。抽象方法和空方法不同。
public abstract void test();
抽象类的构造器不能创建实例,仅用于子类调用,子类必须重写抽象方法。所以final、private和abstract不能同时使用,static和abstract不能同时修饰方法,可同时修饰内部类。
接口
接口作为特殊的抽象类,是一种规范,其不提供任何方法实现。所有方法都是抽象方法,Java8允许定义默认方法(default修饰,可以提供实现)。
接口使用interface关键字定义。
接口只能继承接口,可以有多个父接口。
接口只能包含静态常量(pubilc static final修饰+指定默认值),只能有抽象方法(默认public abstract,普通方法不能有方法实现,类方法和默认方法必须实现),只能定义内部类/接口/枚举(默认public static)。
访问控制符只能使用public或者省略。
一个类可以实现多个接口,关键字用implements,放在extends后面。
实现类必须重写接口所有的抽象方法,必须用public修饰。
比较抽象类和接口
Java8增强的包装类
为了将八种基本数据类型变成引用类型,继承Object类,Java提供了包装类,对应关系如下:
byte - Byte
short - Short
int - Integer
long - Long
char - Character
float - Float
double - Double
boolean - Boolean
自动装箱和自动拆箱
可以直接将一个基本类型的变量赋值给对应的包装类变量,或者赋值给Object变量,自动拆箱则相反。
字符串转换成基本类型的值
利用除Character类的包装类提供的静态方法
int it1 = Integer.parseInt("12345");
利用包装类提供的构造器
float ft1 = new Float("12345");
基本类型的值转换成字符串
利用连接符+
String str1 = 10 + "";
利用String类重载的valueOf()方法
String str2 = String.valueOf(true);
包装类的实例可以与基本数据类型的值进行比较
自动装箱缓存问题(见java.lang.Integer类的源代码)
将-128~127之间的整数自动装箱成Integer实例时,永远都是引用cache数组中的同一个数组元素,而在范围之外的值自动装箱时,总是会创建新的Integer实例,不相同。
Object类
Java所有的类默认继承Object父类,因此所有的类都可调用父类的方法。
重写toSpring()方法输出类的对象的状态信息public String toString(){
return "success";
}
==和equals方法
运算符
如果两个数值类型(不一定数据类型严格对应)相等则返回true;如果两个引用变量指向同一对象则返回true,不能比较非父子关系的两个对象。
关于常量池
在编译时被确定并保存在.class文件中的字符串常量和类、接口、方法中的常量保存在常量池中。常量池中的常量是唯一的。new Integer(12)实际上定义了两个对象。
class EqualTest{
public void compare() {
String str1 = "123";
String str2 = "123";
String str3 = new String("123");
String str4 = new String("123");
System.out.println(str1 == str2);
System.out.println(str1 == str3);
System.out.println(str3 == str4);
System.out.println(str1.equals(str2));
System.out.println(str1.equals(str3));
System.out.println(str3.equals(str4));
}
}
输出true、false、false;true、true、true。
- equals方法
- 如果两个对象的值相等则返回true。
- String已经重写了equals方法,对于其他的Object类equals方法等同于==(都是比较对象的地址),如果想自定义可以重写equals方法。
- equals方法的前一个变量不能为null,如"123".equals(str)
public class Test {
String id = "213";
public String getId() {
return id;
}
public boolean equals(Object object) {
if(this == object)
return true;
if(object != null && object.getClass() == Test.class) {
Test test = (Test)object;
if(this.getId().equals(test.getId())) {
return true;
}
}
return false;
}
}
equals要求两个对象是同一个类的实例,所以用object.getClass()==Test.class代替instanceof,这里用到了反射基础。
3. getClass()方法
返回该对象的运行时类。
4. 控制线程的暂停和运行的方法
wait()、notify()、notifyAll()
5. protected修饰的clone()方法
自定义类实现克隆如下:
//自定义类实现Cloneable接口
public class User implements Cloneable{
//自定义实现clone()方法
public User cloneUser() throws CloneNotSupportedException {
//返回对象的副本
return (User)super.clone();
}
}
使用克隆数组比copy方法快。
Objects工具类
提供空指针安全的方法来操作对象。
String、StringBuffer和StringBuilder类
String类是不可变类,所以会额外产生很多临时变量
int length()方法返回字符串长度
charAt(int index)方法返回指定位置的字符
int compareTo(String str)方法比较两个字符串的大小,返回长度差或第一个不相同字符的差
boolean contentEquals()方法比较两个字符串是否相同
byte[] getBytes()方法将字符串转换成数组
。。。
StringBuffer类和StringBuilder类相似,其对象字符串序列可变,前者线程安全,后者性能略高
append(String string)方法追加字符串
insert(int index, String string)方法插入字符串
reverse()方法反转字符串
setCharAt()方法
setlength()方法设置长度
replace(left, right, string)替换字符串
toString()方法将StringBuffer对象转换为String对象
几个面试题
请问String s = new String("hello");创建了几个对象。
两个。一个"hello"字符串对象,在方法区的常量池;一个s对象,在栈内存。
请写出下面的结果
String s1 = new String("abc");
String s2 = new String("abc");
String s3 = "abc";
String s4 = "abc";
syso(s1==s2); //false
syso(s1==s3); //false
syso(s3==s4); //true
字符串对象一旦被创建就不能被改变。
指的是字符串常量值不改变。
正则表达式
Math类
其构造器为private修饰,提供大量类变量(如PI、E)和类方法。
- pow()方法计算乘方
- random()方法返回一个伪随机数(0.0-1.0)
Random类和ThreadLocalRandom类
Random类生成的是一种伪随机数,相同的种子相同的方法会产生相同的随机数,因此使用当前时间作种子
Random rand = new Random(System.currentTimemillis());
使用ThreadLocalRandom在并发环境下具有更好的线程安全性,它通过current()方法生成对象
ThreadLocalRandom rand = ThreadLocalRandom.current();
- 生成指定范围的随机数
int tmp = rand.nextInt(5, 10);
BigDecimal类
Java的float和double类型会引起精度丢失,BigDecimal类提供大量构造器创建对象。
- 优先推荐使用参数为String的构造器。
- 如果必须使用浮点数为参数的构造器,需要使用valueOf(double val)静态方法转换为BigDecimal对象
BigDecimal f = BigDecimal.valueOf(12.45);
- 基本运算有add()方法、subtract()方法、multiply()方法、divide()方法、pow()方法等
Calendar类
抽象类,调用getInstance()静态方法获取对象。
Calendar cal = Calendar.getInstance();
获取日期cal.getTime()
修改日历指定字段的值cal.set(2013, 12, 20, 06, 30, 42)
add()
roll()
java8新增了java.time包,含如下类:
Clock类可取代System类的currentTimemillis()
LocalDate类不带时区的日期,提供静态now()方法获取当前时刻
LocalTime类不带时区的时间
DayOfWeek枚举类定义了周六到周日
Month枚举类定义了十二个月
单例类
不允许自由创建该类的实例,只允许该类创建一个实例。
public class Singleton {
//需要被static方法访问的缓存变量
private static Singleton singleton = null;
//隐藏构造器
private Singleton() {
}
//暴露的方法,因为调用该方法之前还没有对象,所以要用static修饰
public static Singleton getSingleton() {
if(singleton == null)
singleton = new Singleton();
return singleton;
}
}
final
final修饰的变量不可被改变,但是final修饰的引用变量所引用的对象的内容可以被改变。final修饰的成员变量必须显式的指定初始值(否则没意义,默认初始化)。
final定义并指定初始值的变量在编译阶段就确定下来,保存在常量池中,程序执行时直接进行宏替换。
final修饰的方法不能被重写,比如Object类中的getClass()方法,
final修饰的类不能被继承。
不可变类
Java提供的八个包装类和java.lang.String类是不可变类,它们的实例变量不可改变。
自定义不可变类需要满足的规则:
使用private和final修饰成员变量
使用带参数的构造器来初始化成员变量
提供getter方法,不能提供setter方法
有必要的话重写Object类的hashCode()和equals方法
保证成员变量引用的对象不可变
如果某个不可变实例经常被使用,需考虑对不可变实例进行缓存,减少系统开销。
System类和Runtime类
系统提供System类和Runtime类与程序的运行平台进行交互。
System类代表当前Java程序的运行平台,它提供了一些代表标准输入、标准输出、错误输出的类变量和用于访问环境变量、系统属性的类方法。
比如用long Tnow = System.currentTimeMillis()返回系统当前时间。
Runtime类代表Java程序的运行时环境,可以访问JVM的相关信息。
内部类
Java8新增的lambda表达式
枚举类
枚举类是一种特殊的类,使用enum关键字, 定义如下:
public enum SeasonEnum {
SPRING, SUMMER, FALL, WINTER;
}
使用如下:
public class EnumTest {
public void getEnum() {
//枚举类默认的values()方法返回所有实例
for(SeasonEnum s : SeasonEnum.values()) {
System.out.println(s);
}
}
public void select(SeasonEnum s) {
//swich的控制表达式可以是枚举类型
switch (s) {
//无需添加枚举类限定
case SPRING:
System.out.println("spring");
break;
case SUMMER:
System.out.println("summer");
break;
case FALL:
System.out.println("fall");
break;
case WINTER:
System.out.println("winter");
break;
}
}
}