面向对象
Java增强的包装类:
Java是面向对象的编程语言,但它包含八种基本数据类型,这8中基本数据类型支不支持面向对象编程,基本数据类型也不具备“对象”的特性:;没有Field,方法可以调用。这样做主要是方便传统程序员,java从C语言体系继承过来的一些影子。
为了解决8种基本类型不能当成Object类型变量使用的问题,java提供了包装类的概念,为8种基本数据类型分别定义了相应的引用类型,称之为基本数据类型的包装类。
基本数据类型 | 包装类 | 基本数据类型 | 包装类 |
byte | Byte | char | Character |
short | Short | float | Float |
int | Integer | double | Double |
long | Long | boolean | Boolean |
除了int和char之外其他基本数据类型只要把首字母大写即可。
把基本数据类型变量包装成包装类实例是通过对应包装类的构造器来实现。8个包装类除了charaacrer之外,还可以通过传入一个字符串参数来构建包装类。
public class Test {
public static void main(String[] arg){
Boolean b1 = true;
Boolean b2 = new Boolean(b1);
Boolean b3 = new Boolean("true");
System.out.println(b1+"\t"+b2+"\t"+b3);
Integer i1 = 4;
Integer i2 = new Integer("234");
// 下面运行时会出现java.lang.NumberFormatException
// Integer i3 = new Integer("ddd");
System.out.println(i1+"\t"+i2);
Float f1 = 434f;
Float f2 = new Float("322.00");
System.out.println(f1+"\t"+f2);
}
}
如果希望获取包装类型的基本数据类型可以通过 XxxValue()实例方法来获取。
Java1.5提供自动装箱和自动拆箱功能,可以简化包装类对象和基本类型变来之间的转换。
包装类可以实现基本类型变量和字符串之间的转换。字符串类型的值转换成基本类型的值有两种方式。
一、利用包装类提供的parseXxx(String s)静态方法,除了Character之外都提供此方法。
二、利用包装类提供的Xxx(String s) 构造器
Integer包装类把-128~127之间的数自动装箱成Integer实例后,放入一个名为cache的数组中缓存起来。利用缓存主要为了提高程序运行性能。
Java7为所有的包装类提供一个静态的compare(Xxx x1,Xxx x2)方法,程序员可以通过此方法来比较两个基本数据类型的大小,包括boolena类型,true>false;
打印对象和toString()方法
Object提供一个toString()方法,该方法返回一个字符串,它的值等于:
getClass().getName() + '@' + Integer.toHexString(hashCode())用来实现“自我描述”的功能,有些时候并不能实现自我描述。程序员需要覆盖toString()方法,来实现“自我描述”。如下:
class Person{
private String name;
private int age;
Person(String name,int age){
this.name = name;
this.age = age;
}
@Override
public String toString(){
return "姓名:"+name+"\n年龄:"+age;
}
}
public class Test {
public static void main(String[] arg){
Person p = new Person("lisi",34);
System.out.println(p);
}
}
如果Person没有复习toString()方法打印结果是:.Person@1fc4bec
复写toString()方法打印结果是:
姓名:lisi
年龄:34
== 和equals 方法
Java程序测试中测试两个变量是否相等有两种方法:一种是利用==运算符,另一种是利用equals方法。
当使用==来判断两个变量是否相等时,如果两个变量是基本类型变量,而且都是数值类型,只要两个变量的值相等,就返回true。对于两个引用类型变量,它们必须指向同一个对象时,==才会返回true,==不可用于判断两个类型上没有父子关系的两个对象。如下:
public class Test {
public static void main(String[] arg){
int it = 65;
float ft = 65.0f;
//输出true
System.out.println(it==ft);
char c = 'A';
//输出true true
System.out.println((it==c)+"\t"+(ft==c));
String str1 = new String("itheima");
String str2 = new String("itheima");
//输出false
System.out.println(str1==str2);
//输出true
System.out.println(str1.equals(str2));
}
}
关于String 类型”itheima” 和new String(“itheima”)的区别?
Java程序直接使用”itheima”这样的字符串直接量,jvm通过常量池来管理这些字符串;当使用new String(“itheima”)时,jvm会先使用常量池来管理”itheima”直接量,在调用String构造函数来创建一个新的String对象,被保存在堆内存中。这就是老毕所说的产生两个对象的原因。
Strring中的equals方法是重写的,判断标准是:只要两个字符串所包含的字符串序列相同,就会返回true,否则返回false;
正确复写equals方法应该满足下列条件:
equals 方法在非空对象引用上实现相等关系:
· 自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。
· 对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。
· 传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
· 一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
· 对于任何非空引用值 x,x.equals(null) 都应返回 false。
类成员:
一个类中只能包含Field、方法、构造器、初始化块、内部类(接口、枚举)五种成员。
被static修饰的类成员属于类,不属于类的实例对象。类Field可以通过类名直接访问,也可以通过实例对象来访问。关于类方法的调用:类方法可以通过类名来访问,也可以通过类实例对象来访问,我们可以通过实例对象为null,来访问类方法。如下:
public class Test {
private static void show(){
System.out.println("通过引用为null的对象访问类方法");
}
public static void main(String[] arg){
Test t = null;
t.show();
}
}
打印结果:
通过引用为null的对象访问类方法
表明可以null对象可以访问他所属类的类成员。
如果null对象去访问实例成员,将会引发空指针异常。
对static关键字有一条非常重要的规则:类成员不能访问实例成员。类成员是属于类的,类成员的作用域比实例成员更大,完全可能当类成员已经初始化完毕,但实例成员还未初始化的情况。
单例类:
如果一个对象从始至终只能创建一个实例,则这个类被称为单例类。(在反射中降到我们可以通过反射的方法来调用类被private修饰的方法,所以这句话有狭隘性)
单例类的构造函数被私有化,通过一个public 方法作为该类访问点用于创建该对象,该方法必须用static修饰,该类还要缓存已经创建的对象,否则该类无法知道是否创建过对象,也就无法保证只创建一个对象,该对象要被静态方法访问,所以该成员也要被static修饰。如下,毕向东所基础视频里面的代码:
写法一:
class Single
{
//保证在内存中只有一个对象,饿汉式
//Single类,一进内存就会被建立
private Single(){
}
private static Single s = new Single();
public static Single getInstance()
{
return s;
}
}
class SingleDemo
{
public static void main(String[] args)
{
Single ss = Single.getInstance();
}
}
写法二:
//对象方法被调用时,才初始化,也叫对象的延迟加载,懒汉式
//类进内存不存在,只有被调用方法时,才建立对象
class Single
{
private static Single s =null;
private Single(){
}
public static synchronized Single getInstance()
{// synchronized 低效
if(s=null)
s = new Single();
return s;
}
}
class SingleDemo
{
public static void main(String[] args)
{
Single ss = Single.getInstance();
}
}
写法二.解决方案:
class Single
{
private static Single s =null;
private Single(){
}
public static Single getInstance()//多线程
{// synchronized 低效
if(s==null)
{
synchronized(Single.class)
{
if(s==null)
s = new Single();
}
}
return s;
}
}
Final修饰符
被final修饰的成员变量,不论是类变量还是实例变量,在程序中都要有一个显示的赋值操作,有且只能有一个。类Field必须在静态初始化块或声明该Field时指定初始化。
实例Field必须在非静态初始化块,声明Field或构造器中指定初始化;
与普通成员变量不同之处是,final成员变量必须有程序员显示初始化,系统不会进行隐式初始化。
系统不会对局部变量进行初始化,局部变量必须有程序员自己显示初始化,被final修饰的局部变量即可以在定义是指定默认值,也可以不指定默认值,如果final修饰的局部变量没有在定义是指定默认值,可以在后面对使用final修饰的局部变量赋值初始化,但只能一次。
Final修饰基本类型变量和引用类型变量的区别
当使用fina修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本数据类型变量不能被改变。对于引用类型变量而言,他仅仅是一个引用,final只保证这个引用变量是引用的地址不会改变,即一直引用同一个对象,但这个对象完全可以发生改变。如下:
public class Test {
public static void main(String[] arg){
final int[] arr = {1,2,3,4};
//报错
//arr = new int[];
for(int i=0;i<arr.length;i++){
arr[i] = 0;
}
for(int tmp:arr){
System.out.print(tmp+" ");
}
}
}
打印结果:
0 0 0 0
被final修饰的引用变量不能被重新赋值,但可以改变引用类型变量所引用的对象的内容。
Final方法
Final修饰的方法本能被复写,如果不希望子类复写父类的某个方法,可以使用final修饰该方法。例如javaObject中的getClass()方法。
Final类
被final修饰的类不能有子类,java.lang.Math类就是一个被final修饰的类。
不可变类:
不可边类的意思就是创建该类的实例后,该实例的Field是不可改变的,java8个包装类和java.lang.String是不可变类,当创建他们的实例后,其实例Field不可改变。
创建不可变类的规则:
一、使用private和final修饰该类的Field
二、提供带参数构造器,用于根据传入参数来初始化类中的Field
三、仅为该类的Field提供get方法
四、在必要的情况下复写父类的hashCode和equlas方法
抽象类:
在某些情况下,某个父类只是知道子类的应该包含那些方法,但是不知道这些子类如何实现这些方法。例如定义一个shape类,这个类应该提供一个计算周长的方法calPerimeter(),但不同的Shape子类对周长的计算方法是不一样的。如何让Shape类包含一个calPerimeter()方法,有无须提供方法实现呢?使用抽象方法即可满足该要求。
抽象方法和抽象类
抽象方法和抽象类必须使用abstract修饰,有抽象方法的类必须定义成抽象类,抽象类可以没有抽象方法;
抽象方法和抽象类的规则:
抽象类必须用abstract修饰符修饰,抽象方法也必须使用abstract修饰,抽象方法不能有方法体。
抽象类不能被实例化,无法使用new关键字来创建实例对象,即使抽象类不包含抽象方法也不能创建实例对象。
抽象类可以包含Field、方法、构造器、初始化块、内部类、枚举类。抽象类的构造函数不能创建实例,主要用于被其子类调用。
含有抽象方法的类只能别定义成抽象类。
定义抽象方法只需要在普通方法上增加abstract修饰符,并把普通方法体去掉即可。抽象方法不能被static修饰,即没有类抽象方法。
抽象类的作用:
抽象类体现的是一种模版模式的设计,抽象类作为多个子类的通用模版,子类在抽象类的基础上进行扩展,改造,但子类在总体上会保留抽象类的行为方式。抽象父类可以只定义需要使用的某些方法,把不能实现的部分抽象成抽象方法,留给子类去实现。父类中可包含需要调用的其他系列方法的方法,这些被调方法即可以留给父类实现,也可以由其子类实现。父类提供的方法只是定义一个通用算法,实现可以自身实现,也可以依赖子类实现。
更彻底的抽象:接口
接口概念:接口是从多个相似类中抽象出来的规范,接口不提供任何实现。接口体现的是规范和实现分离的设计哲学。让规范和实现分离正是接口的好处,软件系统的各组件之间面向接口耦合,是一种松耦合的设计。软件系统的个个模块之间也应该采用这种面向接口的耦合,从而降低各模块间的耦合,位系统提供更好的维护和可维护性。
接口定义的多个类共同的公共行为规范,这些行为是与外部交流的通道,这就意味着接口里通常是定义一组公共方法。
接口的定义:
和类定义不同接口定义不再使用class关键字而是使用interface关键字。基本语法格式如下:
[修饰符] interface 接口名 extends 接口1 ,接口2, 接口3 ...
{
零到多个常量定义...
零到多个抽象方法定义...
}
修饰符可以是public或者省略,如果省略public访问控制符,则默认采用包权限访问控制符,即只有在相同包结构下才可以访问该接口
接口名应采用和类相同的命名规则。
一个接口可以继承多个接口,但接口只能继承接口,不能继承类。
接口不能包含构造器和初始化块,接口里可以包含Feild(只能是常量)、方法(只能是抽象方法)、内部类(内部接口、枚举)定义。
接口中定义Field默认添加public static final修饰。接口没有初始化块,因此接口定义的Field只能在定义时指定默认值。
接口中定义的方法,是抽象方法,系统默认增加abstract修饰符,因此接口不允许定义静态方法,不管定义接口时是否使用public abstract修饰符,接口里的方法总是使用public abstract来修饰。
接口里定义的内部类、接口、枚举类默认都采用public static 修饰,不管定义是是否指定这两个修饰符,系统都会自动使用public static对其进行修饰。
public interface Shape{
int MAX = 0xffff;
double getArea();
}
定义了一个Shape接口,这个接口包含一个常量Field:MAX,定义了一个抽象方法表示去的面积的getArea方法。
其中的MAX可以在任何类下面被访问。
public class Test {
public static void main(String[] args){
System.out.println(jst.Shape.MAX);
//下面将会引起异常
//jst.Shape.MAX = 12;
}
}
接口的继承:
接口支持多继承,而类支持单继承,一个接口可以有多个直接父类。和类继承相似,子接口扩展某个父接口,将会获得父接口里定义的所有抽象方法、Field、内部类和枚举类。
接口和抽象类:
接口和抽象类都不能被实例化,他们都位于继承树的顶端,用于别其他类实现和继承
接口和抽象类都可以包含抽象方法,实现接口或继承类的普通子类都必须实现这些抽象方法。
接口里只能包含抽象方法,不包含已经提供的方法;抽象类则完全可以包含普通方法。
接口里不能定义静态方法;抽线类里可以定义静态方法。
接口里只能定义常量Field,不能定义普通Field;抽象类可以随便定义普通Field和静态Field。
接口里不包含构造器,抽象类可以包含构造器,抽象类里的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操纵。
接口里不能包含初始化块,但抽象类中则完全可以包含初始化块。
一个类只能有一个直接父类,包括抽象类;但一个类可以直接实现多个接口,通过实现多个接口可以拟补java单继承的不足。(C语言支持多继承)
内部类:
把一个类放如另一个类的内部定义我们称之为内部类,包含内部类的类被称之为外部类。内部主要有一下几个作用:
内部类提供更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类。
内部类成员可以直接访问外部类的私有数据。因为内部类被当成外部类成员。
内部类适合用于创建那些仅需要一次使用的类。
非静态内部类和静态内部类
内部类定义的语法格式如下:
public class Outerclass{
//此处定义内部类
}
一般情况下内部类是当作成员内部类来定义,而不是局部内部类。成员内部类是一种和Field、方法、构造器和初始化块相似的类成员;局部内部类和匿名内部类则不是类成员。
成员内部类分为两种:使用static修饰的成员内部类是静态内部类,没有使用static修饰的内部类是非静态内部类。
非静态内部类中不能使用static修饰符,可以使用public、private、protected修饰符。非静态内部类通过OuterClass.this.FielName 访问外部类的实例Field,通过OuterClass.FieldName访问外部类的静态Field,通过this.FieldName访问内部类的静态和实例Field。通过new OuterClass().FieldName访问外部类实例Field。
静态内部类可以使用static修饰符。其访问外部类的Field方式和非静态内部了一致。
如下:
class OuterClass {
private String str = "OuterClass";
private static String s = "staticString";
public static class InnerClass1 {
private static String s = "StaticInnerClass";
private String str = "InstanceInnerClass";
public void show() {
System.out.println("静态内部类非静态方法show");
String s = "局部变量s";
// 访问外部类的静态Field 类名.变量名 访问静态内部类的Field
System.out.println("外部类静态Field"+OuterClass.s + "\n静态内部类静态Field" +
this.s+"\n静态内部类Field"+this.str+"\n局部Field"+s);
// 可以通过此种方式访问外部类的非静态Field
System.out.println("外部类非静态Feild"+new OuterClass().str);
// 下面两句将会编辑报错
// System.out.println(OuterClass.str);
// System.out.println(OuterClass.this.str);
}
public static void print() {
System.out.println("静态内部类静态方法print");
}
}
public class InnerClass2 {
private String str = "InstanceInnerClass";
// 不能定义静态Field
// private static String s ="StaticInnerClass";
public void show() {
String str = "局部变量str";
System.out.println("非静态内部类show方法");
// 访问外部类非静态Field 和静态Field
System.out.println("外部类的静态Field"+OuterClass.s + "\n外部类Field" + OuterClass.this.str
+ "\n非静态内部类变量" + this.str+"\n局部变量"+str);
}
// 不能定义静态方法
// public static void print(){}
}
public void show() {
System.out.println("---外部类show方法---");
System.out.println(InnerClass1.s);
System.out.println(new InnerClass2().str);
}
}
使用内部类:
在外部类内部使用内部类,和使用平常类没什么区别。可以通过内部类名直接定义变量,通过new调用内部类的构造器来创建实例。唯一的区别是不要在外部类的静态成员中使用非静态内部类。
在外部类以外使用非静态内部类,如果希望外部类以外的地方访问内部类,则内部类不能使用pirvate访问控制权限,private修饰的内部类只能在外部类的内部使用。
在外部类以外的地方定义内部类(包括静态和非静态)的语法格式如下:
OuterClass.InnerClass varName
在外部类以外的地方那个创建非静态内部类的实例对象语法格式如下:
OuterClass.InnerClass Iner = new OuterClass().new InnerClass();
在外部类以外的地方那个创建静态内部类的实例对象语法格式如下:
OuterClass.InnerClass Iner = new OuterClass.new InnerClass();
如下:
public class Test {
public static void main(String[] args) throws Exception{
OuterClass.InnerClass1 In1 = new OuterClass.InnerClass1();
In1.show();
OuterClass.InnerClass2 In2 = new OuterClass().new InnerClass2();
In2.show();
OuterClass Out1 = new OuterClass();
Out1.show();
}
}
局部内部类:
把一个内部类放在方法中定义,则这个内部类就是一个局部内部类,局部内部类仅在该方法内有效。由于局部内部类不能再方法以外的地方使用,因此局部内部列不能使用访问控制符和static修饰符。
public class Test {
public static void main(String[] args) throws Exception{
final int a = 5;
class Base{
int x = 4;
}
class Sub extends Base{
int y = 5;
public void show(){
System.out.println(x+"\t"+y+"\t"+a);
}
}
Sub s = new Sub();
System.out.println(s.x+"\t"+s.y);
s.show();
}
}
当局部内部类去访问方法中的变量时,这个变量一个要被final修饰。
匿名内部类:
匿名内部类适合创建只需要使用一次的类。定义匿名内部类的格式如下:
new 父类构造器 (实参列表)|(实现接口){
//匿名内部类的类体部分
}
匿名内部类必须继承一个父类,或实现一个接口,但最多只能继承一个父类,或实现一个接口。有如下两条规则:
匿名内部类不是抽象类,因为系统在创建匿名内部类时,会立即创建匿名内部类的对象,因而不允许将匿名内部类定义成抽象类。
匿名内部类不能定义构造器,因为匿名内部类没有类名,所以无法定义构造器,但匿名内部类可以定义实例初始化块,通过实例初始化来完成构造器需要完成的事情。
自定义排序比较是一般使用匿名内部类,实现Comparator接口。
自定义start()开启线程中commod需要实现runable接口。
枚举类:
在某些情况下,一个类的对象是有限而固定的,比如季节,星期,行星等。
和前面的说到的单例类相类似,不让程序员可以随便使用new 来创建对象。
手动枚举类:
手动枚举类采用如下设计方式:
通过private把构造器隐藏起来。
把类的所有可能实例都用public static final 修饰的类变量来保存。
可以提供一些静态方法,允许其他程序根据特定参数来获取与之匹配的实例。
如下定义一个Season类:
public class Season {
private final String name ;
private Season(String name ){
this.name = name;
}
public static final Season Spring = new Season("春季");
public static final Season Summner = new Season("夏季");
public static final Season Fall = new Season("秋季");
public static final Season Winter = new Season("冬季");
}
Season是一个不可变类,当其他程序需要使用Season对象时,只能通过Season.Xxx来获取Season对象。
Java5新增一个enum关键字,用以定义枚举类。枚举是一个特殊的类,有自己的Field、方法,可以实现接口,可以自己定义构造器。枚举类和其他普通类的区别:
枚举可以实现一个或多个接口,使用enum继承了java.lang.Enum类,而不是继承Object。其中java.lang.Enum类实现了java.lang.Serializable和java.lang.Comparable接口。
使用enum定义、非抽象的枚举类会默认使用final修饰,因此枚举类不能派生。
枚举类的构造器只能使用private访问控制符。如果省略默认使用private修饰。如果指定只能指定private修饰符。
枚举类的所有实例必须在枚举类的第一行显式列出,否则这个枚举类永远都不能产生实例。列出这些实例时,系统自动添加public static final修饰。如下:
enum Season {
Spring,Summer,Fall,Winter;
}
枚举类常用的方法:
int comparteTo(E o);该方法用于指定枚举对象比较顺序
String name():返回此枚举类的名称
int ordinal():返回此枚举值在枚举类中的索引值
String toString():和name方法相似
public static <T extends Enum<T>> T valueOf( Class<T> enumType, String name):用于返回指定枚举类中的指定名称的枚举值
枚举类的Field、方法、构造器:
public enum TrafficLamp
{
RED(30){
public TrafficLamp nextLamp()
{
return GREEN;
}
},GREEN(45)
{
public TrafficLamp nextLamp()
{
return YELLOW;
}
},YELLOW(5){
public TrafficLamp nextLamp()
{
return RED;
}
};
public abstract TrafficLamp nextLamp();
private int time;
private TrafficLamp(int time){
this.time = time;
}
}