类和对象
面向对象程序设计(简称OOP)
由类构造对象的过程称为创建类的实例。
封装(有时称为数据隐藏),封装不过是将数据和行为组合在一个包中,并对对象的使用者隐藏了数据的使用方式。对象中的数据称为实例域,操纵数据的过程称为方法。
实现封装的关键在于绝对不能让类中的方法直接的访问其他类的实例域。程序仅通过对象的方法与对象进行交互。封装给予了对象黑盒的特征。
类之间的关系—依赖、聚合、继承
依赖
如果一个类的方法操纵另一个类的对象,我们就说一个类依赖于另一个类。
应该尽可能将相互以来的类减至最少,如果类A不知道类B的存在,它就不会关心B的任何改变。(这意味着B的改变不会导致A产生任何BUG),就是让类之间的耦合度降到最低。
聚合
聚合就是“has-a”的关系。比如订单对象,包含商品信息对象。聚合关系意味着类A对象包含类B的对象。
继承
继承就是“is-a”的关系。就是父子关系,子类包含父类的方法,也拥有自己的方法。
继承使用关键字extends
在使用继承时候应该注意:子类不能够直接访问父类的私有成员,
子类可以调用父类中的非私有方法,但不能直接调用父类中的私有成员。
(可以间接通过setter或getter访问)
子类对象的实例化过程
在继承的操作中,对于子类对象的实例化也是有要求的:子类对象在实例化之前必须首先调用父类中的无参构造方法,
然后再调用子类中的构造方法。
public class Student extends Person{
private String school;
public Student() {
//super(); //加和不加都一样的效果,默认调用父类的构造方法
System.out.println("子类中的构造方法");
}
}
继承中有一个概念是方法的覆写:方法名称,参数类型,返回值类型,全部相同,被覆写的方法不能
拥有更高的权限。(发生在继承中)
方法重载:方法名称相同,参数类型个数不同,没有权限要求。(发生在类中)
private<default<public
在java中没有类就无法做任何事,并不是所有的类都具有面向对象的特征,比如Math类。在使用Math类中的方法Math.random并只需要知道方法和参数名和参数,而不必了解它的具体实现过程。这正是封装的关键所在,所有类都是这样。但遗憾的是,Math只封装了功能,他不需要也不必要隐藏数据。由于没有数据,也不必担心生成对象和初始化实例域。
用final定义的类
对象与对象变量
栈内存实际上保存的堆内存空间的访问地址。
一个对象在使用之前一定要进行实例化。没有实例化就调用对象的方法或者属性会报
异常:Exception in Thread "main" java.lang.NullPointerException
类属于引用数据类型,引用数据类型就是指一段
要想使用对象,就必须首先构造对象,并指定其初始状态。然后对对象施加方法。
在java语言中使用构造器构造新的实例。构造器是一种特殊的方法,用来构造并初始化对象。
Date date=new Date();
对象变量和对象存在一个重要的区别:
Date date=new Date();和Date date2;
date2可以引用Date类型的对象,但是一定要知道,date2不是一个对象,实际上也没有引用对象,不能将任何Date方法应用在这个变量上。要想使用必须初始化(new方式或者引用一个已存在Date对象)
一定要认识到:一个对象变量并没有实际包含一个对象,而仅仅引用一个对象。在java中任何对象变量的值都是对存储在另外一个地方的一个对象的引用。new操作符的返回值也是一个引用。
可以显式的将对象的变量设置为空,表示当前对象变量没有引用任何对象。
变量不会自动进行初始化,必须通过调用new或者将它们设置null进行初始化。
Date类有一个特定的状态,即特定的时间点。其实就是距离指定时间的毫秒数(可正可负)。这个点就是所谓的纪元。它的UTC时间是1970年1月1日 00:00:00。UTC时间是一种有实际意义目的的科学标准时间。
Date类还有getDay、getMonth、getYear等方法不建议使用,因为以后有可能会被JDK废弃掉。
可以用GregorianCalendar来实现某些功能。
GregorianCalendar time=new GregorianCalendar(); //构建一个日历类
int month=time.get(Calendar.MONTH); //取得当前月份(日历类月份显示为0-11)
Date date=time.getTime(); //用日历类获取当前时间。返回值为Date
java编译器
假设有两个类(UserLog,User),java可以怎么编译,两种方式:
1.可以用javac User*.java
2.可以用javac UserLog.java
使用第二种方式并没有显示的编译User类。然而,当java编译器发现UserLog类使用了User类时,会查找名为User的class文件,如果没有找到这个class文件,就会自动搜素User.java文件并进行编译。如果User有新的版本,java编译器就也会重新自动编译这个文件。
java构造器
构造器与类同名
每个类可以有一个以上的构造器
构造器可以有任意数量的参数
构造器没有返回值
构造器总是伴随着new操作一起调用。
匿名对象
public static void sum(int num) {
new Test();
}
new Test();就代表匿名类,直接创建在堆内存中,用过之后,就会被java的垃圾回收机制收走。
匿名对象在实际开发中,基本上都是作为其他类实例化对象的参数传递的。
方法参数
一个方法不能修改一个基本数据类型的参数。
一个方法可以改变一个对象参数的状态。
一个方法不能实现让对象参数引用一个新的对象。
final类
1.使用final声明的类不能有子类
2.使用final声明的方法不能被子类覆盖
3.使用final声明的变量就是常量,不能修改。
如果在定义类的时候,使用了final修饰符,就表明这个类时final类。并且你可以继承final类,但是不能重写和修改这个类的原来的方法。(类是final的,里面的方法自动变成成为final)
将方法或者类声明为final的主要原因:确保他们不会在子类中语义改变,比如Calendar类中的getTime和setTime方法都声明为final。这表明Calendar设计者负责实现Date类与日历状态之间的转换,而不允许子类处理这类的问题。同样的String类也是final类,这意味着不允许任何人定义String的子类。换言之,如果有一个String的引用,它引用的一定是一个String对象,而不可能是其他类的对象。
在早期有些程序员为了避免动态绑定带来的系统开销而使用final关键字。如果一个方法没有被覆盖并且很短,编译器就会很快的优化处理,这个过程称为内联。如果一个方法在另一个类被覆盖,那么编译器就无法知道覆盖的代码需要做什么,因此也就无法对它进行内联处理了。
幸运的是,虚拟机的及时编译比传统编译器的处理能力强的多,这种编译器可以准确的知道类之间的继承关系,并且能够检测出类中是否真正存在覆盖给定的方法。如果方法很简短,被频繁调用且没有真正覆盖,那么编译器就将这个方法进行内联处理。如果虚拟机加载了另外一个子类,而在这个子类中包含对内联方法的覆盖,那么将会发生什么情况呢,优化器将取消覆盖方法的内联,这个过程很慢,但却很少发生。
抽象类
抽象方法充当着占位的角色,他们的具体实现在子类中。拓展抽象类有两种选择,一种是在子类中定义部分抽象类方法或者抽象方法也不定义,这样就必须将子类也标记为抽象类,另一种是定义全部的抽象方法,这样一来子类就不是抽象的了。类即使不含抽象方法也可以将类声明为抽象类。
抽象类不能被实例化。也就是说,如果一个类声明为abstract,就不能创建这个类的对象。
抽象类:
通过继承可以从原有的类派生出新的类,原有的类称为基类或者父类。而新的类则称为派生类或子类。通过继承
机制,派生出来的类不仅可以保留原有类的功能,而且可以拥有更多的功能。
除了上述机制之外,在java中也可以创建一种类,用来完全当做父类,这种类称为抽象类。抽象类的作用类似于
模板,其目的是设计者依据它的格式来修改并创建新类。但是并不能由抽象类创建对象。只能通过抽象类派生出
新的类,再由它创建对象。但是抽象类在使用中仍然存在局限,就是一个类只能继承一个抽象类。
1.包含一个抽象方法的类必须是抽象类
2.抽象类和抽象方法必须使用abstract关键字声明。
3.抽象方法只需声明不需要实现。
4.抽象类必须被子类继承,子类如果不是抽象类,必须覆写抽象类中的全部抽象方法。
实际上抽象类和普通类只多了一个或多个抽象方法,除了不能直接实例化对象外,并没有什么其他区别。
抽象方法不要私有(private),这样子类无法覆写。
toString,这个方法我们应该在自定义类中覆盖它,不覆盖会默认使用Object的toString方法。返回该对象的字符串。
接口
接口可以被理解成一种特殊的类,是由全局常量和公共的抽象方法组成。
形式: interface 接口名称{
全局常量;
抽象方法;
}
在接口中的抽象方法必须是public权限的,这是绝对不可改变的。默认不写也是public权限,而不是default。
和抽象类一样,接口要使用的话,也必须通过子类,子类通过implements关键字实现接口。
一个子类同时可以实现多个接口
接口可以被多继承。
对象的多态性
在java面向对象主要有两种体现:
1.方法的重载与重写
2.对象的多态性
对于对象的多态性主要分为以下 两种类型:
1.向上转型:子类对象----->父类对象
2.向下转型:父类对象----->子类对象
对于向上转型,程序会自动完成。对于向下转型,必须明确指明要转型的子类类型。
对象向上转型:父类 父类对象 = 子类实例
对象向下转型:子类 子类对象 = (子类)父类实例
工厂模式
public static void Fruit getInstance(String className){
Fruit f = null;
if("apple".equals(className)){
f=new Apple();
}
if("orange".equals(className)){
f=new Orange();
}
retutn f;
}
为什么在进行字符串判断时,要把字符串常量写在前面。
if(“apple”.equals(className))这样可以避免空指针异常。
因为前面的事字符串常量,对比时候不可以为空。后面的equals()括号里面代表字符串对象,对象可以为空。
如果这样的话:String str=null;
“hrllo”.equals(str);
这样程序不会报错,结果会是false。
代理设计
Network net=null;
net=new Proxy(new Real()); //实例化代理类,同时传入代理的真实操作。
net.方法(); //处理客户的业务。
适配器设计
如果一个类要实现一个接口,必须覆写此接口的全部抽象方法。那么接口那么多的抽象方法,我们不是都能用的,这时候我们只想继承部分抽象类,实现部分接口中的方法。这时候就可以创造一个适配器类。
其实这个适配器类就是创建一个抽象类,然后实现接口中的所有方法(但是方法体全是空的,只有一对大括号{}),然后我们真正的业务区去继承这个适配器,就是我们之前创造的那个抽象类。这时候我们就可以有选择的覆写我们需要的方法了。
instanceof关键字
java中可以使用instanceof关键字来判断一个对象到底是哪个类的实例。
形式:对象引用 instanceof 类 返回boolean类型
在类的设计时,永远不要去继承一个已经实现好的类,只能继承抽象类或者实现接口,因为一旦发生对象向上转型后,所调用的方法一定是被子类覆写过的方法
递归
public static int sum(int num) {
if(num==100) {
return 100;
}else {
return num+sum(num+1);
}
String和int、Integer转换
String直接赋值和new String()方式比较
直接赋值代表直接在堆内存创建了一个匿名对象空间。这样即使下次声明同样内容的变量,也不会创建新的堆内存对象了。
new String()这种方式,不管怎么样都会创建一个新的堆内存空间对象的,内容和之前的一样也会创建的。
static String toString(int i)
static int parseInt(String s)
static Integer valueOf(String s)
参数可变,如Object…,三个点就代表可以接收任意数量的对象
反射
反射库提供了一个非常丰富且精心设计的工具集,以便编写能够动态操纵java代码的程序,这项功能被大量的应用于JavaBean中。
能够分析类能力的行为称为反射
在运行中查看类的对象
实现数组的操作代码
链表
链表类 data存储数据 next下一个节点
class Outer{
private String data;
private Outer next;
public Outer(String data) {
this.data=data;
}
public void setNext(Outer next) {
this.next=next;
}
public Outer getNext() {
return this.next;
}
public String getData() {
return this.data;
}
}
递归循环调用打印
public class Test2 {
public static void main(String[] args) {
//递归取链表中的数据
Outer node1=new Outer("车头-1");
Outer node2=new Outer("车头-2");
Outer node3=new Outer("车头-3");
Outer nod4=new Outer("车头-4");
node1.setNext(node2);
node2.setNext(node3);
node3.setNext(nod4);
getRecursive(node1);
}
public static void getRecursive(Outer node) {
System.out.println("节点----"+node.getData());
if(node.getNext()!=null) {
getRecursive(node.getNext());
}
}
}
打印结果:
节点----车头-1
节点----车头-2
节点----车头-3
节点----车头-4
阶段总结
1.面对对象的三大特征为:封装、继承、多态
2.类与对象的关系:类是对象的模板,对象是类的实例。类只能通过对象才能使用。
3.类是由属性和方法组成的
4.如果一个对象没有被实例化而直接使用,则使用时会出现空指针异常。
5.类属于引用数据类型。
6.类的封装性:通过private关键字进行修饰,被封装的属性不能被外界直接调用,而只能通过setter和getter方法完成,只要是属性,则必须全部封装。
7.构造方法可以为类中的属性初始化,构造方法与类名称相同,无返回值类型声明,如果在类中没有明确定义出构造方法,则会自动生成一个无参的构造方法,在一个类中构造方法可以重载,但是每一个类中至少有一个构造方法。
8.String类在java中比较特殊,String可以使用直接赋值的方式,也可以通过构造方法进行实例化,前者只产生一个实例化对象,而且实例化对象可以重用,后者将产生两个实例化对象,其中一个是垃圾空间,在String中比较内容用equals方法,而“==”用来比较两个字符串的地址是否相同,字符串的内容一旦声明不可改变。
9.在java中使用this关键字可以表示当前对象,通过“this.属性”,可以调用本类中的属性,通过“this.方法”可以调用本类中的其他方法,也可以通过"this()"的方式调用本类的构造方法。但是调用要求放在构造方法的首行。
10.使用static声明的属性和方法可以由类名称直接调用,static属性是所有对象共享的,所有对象都可以对其进行操作。
11.如果要限制类对象的产生,则可以将构造方法私有化。
12.对象数组的使用要分为声明数组和为数组开辟空间两步,开辟空间后,数组的每一个元素都是null
13.内部类是在一个类的内部定义另外一个类,使用内部类可以方便的访问外部类的私有操作。在方法中声明的内部类要想访问方法的参数,则参数前必须加上final关键字。
1.继承可以扩充已有类的功能,通过extends关键字实现,可将父类的成员(包含数据成员和方法)继承到子类
2.java在执行子类的构造方法前,会先调用父类的无参构造方法,其目的是为了对继承自父类的成员做初始化
操作。
3.父类有多个构造方法时,如要调用特定的构造方法,则可在子类的构造方法中通过super()关键字来实现。
4.this()用于在同一类内调用其他的构造方法,而super()则用于从子类的构造方法中调用其父类的构造方法。
5.使用this调用属性或者方法时,会先从本类中查找,如果本类中没有找到,再从父类中查找。
6.this()与super()相似之处在于:
(1)当构造方法有重载时,两者均会根据所给予的参数类型与个数正确的执行相对应的构造方法。
(2)两者均必须编写在构造方法的第一行,也正是这个原因this()与super()无法同时在一个构造方法内
7.重载是指在相同类内方法名相同,方法的参数个数和类型不同的方法。
8.覆写是指在子类中定义名称、参数个数、方法类型均与父类相同的方法,用以覆写父类的方法。
9.如果父类的方法不希望被子类覆写,可在父类的方法前加上final关键字,这样该方法便不会被覆写。
10.final的另一个功能是把它加在数据成员前面变成一个常量,因此便无法在程序代码中再做修改,
使用public static final可以声明一个全局常量。
11.所有的类均继承自Object类,一个好的类应该覆写Object类中的toString(),equals(),hashCode()
三个方法。所有的对象都可以向Object进行向上转型。
12.java可以创建抽象类,专门用来当做父类。抽象类的作用类似于模板,其目的是依据其格式来修改并创建
新的类。
13.抽象类的方法分为两种,一种是一般方法,一种是以abstract关键字修饰的抽象方法。抽象方法
并没有定义方法体,而是要保留给由抽象类派生出的新类来定义。
14.抽象类不能直接产生对象,必须通过对象的多态性进行实例化操作。
15.接口是方法和全局常量的集合,接口必须被子类实现,一个接口可以同时继承多个接口,
一个子类同时可以实现多个接口。
16.java并不允许类的多重继承,但是允许实现多个接口。