------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------
一、面向对象
1,面向对象是一种常见思想,符合人们的思考方式,将复杂的问题简单
主要关注事物的两个方面:属性和行为。
对象就是一个事物实实在在存在的个体。
类:事物的描述。对象:该类事物的实例,在java中通过new来创建。一个简单示例如下:
public class ObjectDemo {
public static void main(String[] args) {
// 通过new关键字创建一个Car的实例
Car car = new Car(); // car是一个类类型的变量,指向该类对象
// 使用对象中的内容可通过 对象.成员的形式来完成
car.num = 4;
car.color = "black";
car.run();<span style="white-space:pre"> </span>// 4...black
}
}
// 类:用来描述事物,主要关注属性和行为两个方面。
class Car{
// 属性---成员变量
int num;
String color;
// 行为---成员函数
void run(){
System.out.println(num+"..."+color);
}
}
2,成员变量与局部变量的区别
a,成员变量定义在类中,整个类中都可以访问;局部变量定义在函数、语句和局部代码块中,
只在所属区域有效。b,成员变量存储在堆内存对象中;局部变量存储在栈内存的方法 中。c,
成员变量随着对象创建而存在,随着对象的消失而消失。局部变量随着所属区域的执行而存在,
随着所属区域的结束而释放。d,成员变量有默认初始化值,局部变量没有默认初始化值。
3,类类型的变量一定会指向对象,否则就指向null。
4,匿名对象:没有名字的对象,其实是定义对象的简化格式。
a,当对象对方法仅进行一次调用的时候,就可以简化成匿名对象。
b,匿名对象可以作为实际参数进行传递。
5,面向对象三大特征:封装、继承和多态。
封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式。
好处:提高安全性、便于使用,提高复用性。
封装的原则:不需要对外提供的内容都隐藏起来;把属性都隐藏,仅对外提供公共方法对其访问。
代码示例:
public class ObjectDemo2 {
public static void main(String[] args){
Person1 p = new Person1();
p.setAge(1000); // 填写1000就会提示:年龄错误
}
}
class Person1{
// private:是一个权限修饰符,用于修饰成员,私有内容只在本类中有效
// 私有是封装的一种体现
private int age;
// 函数是java中最小的封装体
public void setAge(int age) {
// 对外提供的访问方法可以对属性进行限制
if(age<0||age>150){
System.out.println("年龄错误");
return;
}
this.age = age;
}
}
6,构造函数:
特点:函数名与类名相同;不用定义返回值类型;没有返回值。
作用:用于给对象进行初始化。创建对象都必须要通过构造函数进行初始化。
在一个类中如果没有定义构造函数,那么该类中会有一个默认的空参构造函数。
如果类中定义了指定的构造函数,那么类中就没有默认的空参构造函数。如果还需要
空参构造函数,那么就显式定义空参构造函数。代码示例:
class Person{
// 定义成员变量,并私有化
private int age;
private String name;
// 构造函数与类名相同,没有返回值类型
// 构造函数是用于给对象进行初始化。
Person(){ // 定义构造函数,空参
System.out.println("Hello World!");
// 没有返回值
}
// 定义成员函数,对外提供访问方法
public void speak(){
System.out.println(name+"---"+age);
}
}
7,一般函数与构造函数的区别
a,构造函数:对象创建时,就会调用与之对应的构造函数,对对象进行初始化。
一般函数:对象创建后,需要函数功能时才调用。
b,构造函数:对象创建时,会调用且只调用一次。
一般函数:对象创建后,可按需求进行多次调用。
8,什么时候定义构造函数?
在描述事物时,该事物一存在就具备的一些内容,这些内容应该定义在构造函数中。
构造函数可以有多个,用于对该类对象进行针对性的初始化,多个构造函数在类中
以重载的形式体现。注意:构造函数带有return语句。演示代码:
class Person{
// 定义成员变量,并私有化
private int age;
private String name;
// 构造函数重载:参考函数名和参数列表(参数个数、参数类型、参数顺序)
Person(){ // 空参构造函数
}
Person(int age){ // 带1个参数构造函数
}
Person(String name){ // 带1个参数构造函数
}
Person(int age,String name){ // 带2个参数构造函数
}
Person(String name,int age){ // 带2个参数构造函数
}
}
9,this关键字
当成员变量和局部变量重名时,可以用this关键字来区分。
this就是所在函数所属对象的引用,代表的是当前对象。简单说,哪个对象调用了
this所在的函数,this就代表哪个对象。
class Person{
// 定义成员变量,并私有化
private int age;
private String name;
// 当成员变量和局部变量重名时,可以用this关键字来区分。
Person(String name,int age){
this.name = name;
this.age = age;
}
}
this也可以用在构造函数中调用其他构造函数,但是
只能定义在第一行。演示代码:
class Person{
// 定义成员变量,并私有化
private int age;
private String name;
// 一个参数的构造函数
Person(String name){
this.name = name;
}
// 两个参数的构造函数
Person(String name,int age){
this(name); //调用其他构造函数,定义在第一行
this.age = age;
}
}
总结:构造函数可以调用一般函数;一般函数可以调用一般函数;
一般函数一般不调用构造函数;构造函数可以调用构造函数(使用this关键字)
10,static关键字
static特点:a,static是一个修饰符,用于修饰成员。b,static修饰的成员被所有该类对象共有。
c,static优先于对象存在,因为static成员随着类的加载而加载。d,static修饰的成员可以被类名
所有调用,调用格式:类名.成员。e,静态修饰的数据时共享数据,对象中存储的是特有数据。
静态变量和成员变量的区别:a,生命周期不同,成员变量随着对象的创建/消失而创建/消失。
b,静态变量随着类的加载/消失而加载/消失。b,调用方式不同,成员变量只能被对象调用;静态
变量既可以被对象调用,又可以被类名调用,建议用类名调用。c,存储位置不同,成员变量存储
在堆内存的对象中,又称对象的特有数据;静态变量存储在方法区(的静态区),又称对象的共享
数据。d,别名不同:成员变量又称实例变量;静态变量又称类变量。
静态使用的注意事项:a,静态方法只能访问静态成员。b,非静态方法既可以访问静态,又可以访问
非静态。c,静态方法中不可以定义this、super关键字。d,主函数是静态的。
class Person{
private int age; // 成员变量--实例变量
private static String name; // 静态变量--类变量
// 静态方法只能调用静态成员,静态方法中没有this关键字
public static void show(){
System.out.println(/*this.*/name); // this不可以用
}
// 一般方法既可以调用静态成员,又可以调用非静态成员
public void method(){
System.out.println(name+"..."+age);
}
}
静态什么时候使用?
静态成员:成员是所有对象共有的,这时可以定义为静态成员
静态方法:方法中只要不涉及到非静态变量,就可以定义为静态成员。
静态代码块:定义在类中,随着类的加载而加载,用于给类初始化,而且只执行一次。
class StaticDemo{
// 静态代码块,随着类加载而加载,只执行一次
static{
System.out.println("hello world ");
}
}
11,构造代码块:定义在类中,随着对象加载而加载,用于给所有对象初始化,可执行多次。
构造代码块与构造函数都是给对象进行初始化,二者的区别是什么呢?构造代码块是用于给所有的
对象进行初始化,而构造函数是对该类指定对象进行特定初始化。演示代码:
class ConsDemo{
String name;
// 构造代码块:用于给该类所有对象进行初始化
{
System.out.println("haha");
}
// 给空参对象进行初始化
ConsDemo(){
System.out.println("heihei");
}
// 给带name的独享进行初始化
ConsDemo(String name){
this.name = name;
}
}
一个类中所有的方法都是静态的,那么就可以将该类的构造函数全部私有化。
12,单例设计模式:保证一个类的对象在内存中的唯一性。如何保证对象的唯一性呢?
思路:a,不允许使用new来创建该类对象。b,在该类中创建一个本类实例并私有化。
c,对外提供一个方法让其他程序可以获取该对象。
// 饿汉式<span style="white-space:pre"> </span>开发时用
class Single{
// 在本类中创建一个本类对象并私有化
private static Single s = new Single();
// 私有化构造函数,让外界无法通过new创建对象
private Single(){
}
// 对外提供访问本类对象的方法。
public static Single getSingle(){
return s;
}
}
// 懒汉式,存在一定的安全隐患,多线程技术可解决此问题
class Single2{
// 在本类中创建一个本类对象的引用指向null
private static Single2 s = null;
// 私有化构造函数,让外界无法通过new创建对象
private Single2(){
}
// 当调用Single2方法时,如果引用为null,则创建对象,不为null,返回引用s
public static Single2 getSingle(){
if(s==null)
s = new Single2();
return s;
}
}
二、面向对象特征之二:继承(extends)
1,继承的好处:提高了代码的复用性;让类与类之间产生了关系;为多态提供了前提。
在java中支持单继承,不支持多继承,对C++中的多继承机制进行了改良(多实现)。
单继承:一个子类只能有一个直接父类。多继承:一个子类可以有多个直接父类。
为什么不支持多继承?因为多个父类中可能有相同的成员,会产生调用的不确定性。
当使用一个继承体系时,查看顶层类,了解基本功能;创建最子类对象,完成功能调用。
什么时候使用继承?
当类与类之间存在所属关系时,可以定义继承。is a的关系
2,子父类中继承关系的体现三个方面:成员变量、成员函数、构造函数。
成员变量:
public class ObjectDemo2{
public static void main(String[] args){
Zi z = new Zi();
z.show1(); //4...5
z.show2(); //5...5
z.show3(); //3...5
}
}
class Fu{
int num = 3;
int num1 = 4;
}
class Zi extends Fu{
int num = 5;
// 子父类中成员变量不同名时,可以正常访问父类中的成员变量
void show1(){
System.out.println(num1+"..."+num);
}
// 子父类中成员变量同名时,不可以直接访问父类中的成员变量
void show2(){
System.out.println(num+"..."+num);
}
// 子父类中成员变量同名时,访问父类成员变量需要加上super关键字
void show3(){
System.out.println(super.num+"..."+num);
}
}
super是代表父类空间,而不是父类对象,另外父类成员变量存储在子类对象中。
成员函数:
public class ObjectDemo2{
public static void main(String[] args){
Zi z = new Zi();
// 子父类中函数不同名时,如果子类中没有,就去父类中找
z.show1(); // fu show run
z.show2(); // zi show run
// 子父类中成员函数一模一样时,会运行子类函数。这种现象称为覆盖操作
z.show(); // zi show run ...
}
}
class Fu{
void show(){
System.out.println("fu show run...");
}
void show1(){
System.out.println("fu show run");
}
}
class Zi extends Fu{
void show(){
System.out.println("zi show run...");
}
void show2(){
System.out.println("zi show run");
}
}
对于子父类中的特性覆盖有以下注意事项:
a,子类方法覆盖父类方法时,子类权限必须大于父类权限。
如果父类的同名方法时private时,这时不称为覆盖
b,静态只能覆盖静态或被静态覆盖。
覆盖操作什么时候用?
当对一个类进行子类功能扩展时,子类需要保留父类的功能声明,但是要定义子类中该
功能特有内容时,就是用覆盖操作来完成。演示代码:
public class ObjectDemo2{
public static void main(String[] args){
new NewPhone().show();
}
}
class Phone{
void show(){
System.out.println("call");
}
}
// 子类对象对父类对象功能的扩展
class NewPhone extends Phone{
// 覆盖父类中的show方法
void show(){
System.out.println("image");
System.out.println("color");
super.show();
}
}
构造函数:
子类构造函数中第一行有一个默认的隐式语句super(),调用的是父类中的空参构造函数。
如果父类中没有空参构造函数,那么子类就需要显示指定父类某个构造函数super(para1,para2...)
public class ObjectDemo2{
public static void main(String[] args){
Zi z = new Zi();<span style="white-space:pre"> </span>// fu run zi run
}
}
class Fu{
Fu(){
System.out.println("fu run");
}
}
class Zi extends Fu{
Zi(){
// super();<span style="white-space:pre"> </span>//默认的隐式语句
System.out.println("zi run");
}
}
当父类中没有空参构造函数时:
public class ObjectDemo2{
public static void main(String[] args){
Zi z = new Zi();<span style="white-space:pre"> </span><span style="font-family: Arial;">// fu run zi run</span>
}
}
class Fu{
Fu(int x){
System.out.println("fu run");
}
}
class Zi extends Fu{
Zi(){
super(3);<span style="white-space:pre"> </span>// 显示指定父类的构造函数
System.out.println("zi run");
}
}
为什么子类对象实例化过程要访问父类的构造函数呢?
因为子类继承了父类,获取了父类中的内容,所以在使用父类内容之前,需要知道父类是如何对
自己进行初始化的,所以子类在创建对象时,必须访问父类中的构造函数,通过super语句实现。
super语句放在第一行的原因:父类必须先完成初始化。
当子类构造函数中有this语句时(必须放在第一行),那么这个构造函数中就没有super了,但可以
肯定的是,子类中某个构造函数中肯定含有super语句。
3,final关键字:可修饰类、方法、变量
final修饰的类不可被继承,修饰的方法不可被覆盖,修饰的变量为常量,只能赋值一次。
此外,内部类只能访问由final修饰的局部变量。
被final修饰的变量,一般也由static修饰,同时要显示初始化。而且一般起一个全为大写的名称。
如果由多个字母组成,中间由”-“连接。
4,抽象类:类由abstract修饰
// 抽象类
abstract class Demo{
// 抽象方法,没有方法体
abstract void show();
}
抽象类特点:
a,方法只有声明没有实现时,该方法就是抽象方法,需要被abstract修饰。抽象方法必须放
在抽象类中,该类也必须被abstract修饰。
b,抽象方法不可以被实例化,因为调用抽象方法没有意义。
c,抽象类必须由其子类覆盖了所有的抽象方法后,才可以对其子类进行实例化。如果没有覆盖
所有的抽象方法,那么该子类仍然是抽象方法。
抽象类的注意事项:
a,抽象类中有构造函数,用于给其子类进行初始化。
b,抽象方法可以不定义抽象方法,目的是不让该类创建对象,AWT适配器。
c,抽象关键字不可以与private,final、static(类名调用抽象方法没有意义)。
d,抽象类与一般类的异同:二者都是用来描述事物的,都在内部定义了成员。
不同的是:一般类有足够的信息描述事物,抽象类描述事物的信息可能不足;一般类中
不能定义抽象方法,只能定义非抽象方法,抽象类都可以定义;一般类可以被实例化,而
抽象类不可以被实例化。
e,抽象类一定是父类吗?是的,如果子类没有完全覆盖父类中的抽象方法,
那么子类仍然是抽象方法。
5,接口:由interfacce修饰。
当一个类中所有的方法都是抽象方法时,这时这个类可以由接口来表示。
// 接口
interface Inter{
// 接口中定义的都是全局常量
public static final double MY_PI = 3.14;
// 方法都是抽象的,且权限是public
public abstract void show();
public abstract void run();
}
接口中的成员都是public,方法都是抽象的。
类与接口之间的关系为实现关系,通过关键字implements,一个类实现一个接口,那么
必须覆盖该接口中所有的抽象方法。
// 接口
interface Inter{
// 接口中定义的都是全局常量
public static final double MY_PI = 3.14;
// 方法都是抽象的,且权限是public
public abstract void show();
public abstract void run();
}
class Impl implements Inter{
// 覆盖接口中所有的抽象方法
public void show() {
}
public void run() {
}
}
一个类可以实现多个接口,弥补了java单继承的局限。
一个类可以继承另一个类的同时,还可以实现多个接口。
interface A{
public abstract void show();
}
interface B{
public abstract void run();
}
// 接口C同时继承了接口A和接口B
interface C extends A,B{
public abstract void method();
}
接口的特点:
接口对外暴露规则,降低了耦合性,实现了功能的扩展。
接口和抽象类的异同点:
相同点:都是不断向上抽取而的得到的。
不同点:
a,抽象类需要被继承,而且只能单继承;接口需要被实现,可以多实现。
b,抽象类中可以定义抽象方法和非抽象方法,子类继承后,可以直接使用非抽象方法。
接口中只能定义抽象方法,必须由子类去实现。
c,抽象类的继承是is a关系,在定义体系的基本共性内容。
接口实现是like a关系,在定义体系的额外功能。
接口的应用练习:
public class PC{
public static void main(String[] args){
useUSB(new Upan()); // 功能得到了扩展
useUSB(new Umouse());
}
public static void useUSB(USB usb){
usb.open();
usb.close();
}
}
interface USB{ // 对外暴露规则
public abstract void open();
public abstract void close();
}
class Upan implements USB{ // 实现了规则
public void open() {
System.out.println("Upan open");
}
public void close() {
System.out.println("Upan close");
}
}
class Umouse implements USB{ //实现了规则
public void open() {
System.out.println("Umouse open");
}
public void close() {
System.out.println("Umouse close");
}
}
三、多态: 父类或者接口指向子类的对象。(简单说:一个对象,两个形态)
1,多态的好处:提高了代码的扩展性,前期定义的代码可以使用后期的内容。
多态的弊端:前期定义的代码不可以使用后期子类的特有内容。
多态的前提:必须有继承或实现关系;必须有覆盖操作。
2,多态的形态转换
父类或接口引用指向子类对象时,子类对象就得到了自动提升(向上转型),这时引用
只能使用子父类中的共性方法。如果想使用子类中的特有方法,则需要讲引用进行向下
转型。演示代码:
public class DuoTaiDemo{
public static void main(String[] args){
// Dog类型被自动提升为了动物类型,向上转型
Animal a = new Dog();
// a只能访问共性方法eat,而不能访问Dog的特有方法hawl
a.eat();
// 如果要访问子类中的特有方法,需要向下转型
// 向下转型的目的就是使用子类中的特有方法
if(a instanceof Dog){ // 一般在转型时,都会加入类型判断
Dog d = (Dog)a;
d.hawl();
}
}
}
abstract class Animal{
public abstract void eat();
}
// 继承了Animal抽象类
class Dog extends Animal{
// 共性方法
public void eat() {
System.out.println("dog eat");
}
// 特有方法
public void hawl(){
System.out.println("dog hawl");
}
}
3,多态--成员特点:
成员变量:
编译和运行都看左边(父类或接口引用),即父类或接口中是否含有调用的成员变量。
public class DuoTaiDemo{
public static void main(String[] args){
// 如果父类中没有num则编译失败
Fu f = new Zi();
System.out.println(f.num); //3
}
}
class Fu{
int num = 3;
}
class Zi extends Fu{
int num = 5;
}
成员函数:编译看左边,运行看右边。
public class DuoTaiDemo{
public static void main(String[] args){
// 编译看左边,运行看右边
Fu f = new Zi();
f.show();
}
}
class Fu{
// 如果父类中没有show方法,则编译失败。
void show(){
System.out.println("fu show");
}
}
class Zi extends Fu{
// 子类没有自己的show方法,编译不会失败,因为其继承了父类的show
void show(){
System.out.println("zi show");
}
}
静态函数:和成员变量一样,编译和运行都看左边。
public class DuoTaiDemo{
public static void main(String[] args){
// 编译看左边,运行看左边
Fu f = new Zi();
f.show();
}
}
class Fu{
// 如果父类中没有show方法,则编译失败。
static void show(){
System.out.println("fu show");
}
}
class Zi extends Fu{
// 子类没有自己的show方法,编译不会失败,因为其继承了父类的show
static void show(){
System.out.println("zi show");
}
}
四、内部类
1,将一个类定义在另一类的内部,这个定义在内部的类称为内部类。
内部类可以直接访问内部类成员包括私有成员。外部类要访问内部类需要创建内部类对象。
public class ClassDemo {
public static void main(String[] args) {
new Outer().show();
// 如果内部类不是私有的,可以直接访问内部类
new Outer().new Inner().show();
}
}
class Outer{
private int num = 3;
class Inner{
void show(){
// 内部类可以访问外部类的成员包括私有的成员
System.out.println("Outer's private num is "+num);
}
}
// 外部类访问内部类,需要先创建内部类对象
void show(){
new Inner().show();
}
}
2,内部类定义在外部类成员位置上,可以由权限修饰符进行修饰。
如果内部类是private,那么再其他类中无法直接访问内部类。
如果内部类是static,相当于一个外部类
如果内部类是static,且定义有static方法,可有外部类名.内部类名.静态方法调用。
import Object.Outer.Inner;
public class ClassDemo {
public static void main(String[] args) {
new Outer().show();
// 如果内部类不是私有非静态的,可以直接访问内部类
// new Outer().new Inner().show();
// 如果内部类是静态的
// Inner in = new Outer.Inner();
// 如果内部类是静态的,且有静态成员
Outer.Inner.function();
}
}
class Outer{
private static int num = 3;
static class Inner{
void show(){
// 内部类可以访问外部类的成员包括私有的成员
System.out.println("Outer's private num is "+num);
}
// 内部类定义了静态成员,那么内部类也应该是静态的
static void function(){
System.out.println("function run");
}
}
// 外部类访问内部类,需要先创建内部类对象
void show(){
new Inner().show();
}
}
3,内部类为什么能访问外部类?因为内部类持有外部类的引用 Outer.this
public class ClassDemo {
public static void main(String[] args) {
new Outer().new Inner().show(); //5...4...3
}
}
// 内部类为什么能访问外部类?因为内部类持有外部类的引用 Outer。this
class Outer{
int num = 3;
class Inner{
int num = 4;
void show(){
int num = 5;
System.out.println(num+"..."+this.num+"..."+Outer.this.num);
}
}
}
4,内部类可以放到局部位置上。只能访问局部中被final修饰的局部变量。
public class ClassDemo {
public static void main(String[] args) {
new Outer().method();
}
}
// 内部类可以放到局部位置上。只能访问局部中被final修饰的局部变量。
class Outer{
int num = 3;
void method(){
final int num = 6;
class Inner{
void show(){
System.out.println(num);
}
}
Inner in = new Inner();
in.show();
}
}
5,匿名内部类:内部类的简写格式,其实是一个匿名子类对象。
匿名内部类的前提:内部类必须继承一个外部类或者实现一个接口。
通常使用的场景之一:当函数参数是接口类型时,而且接口中的方法不超过三个,可以
用匿名内部类作为实际参数进行传递。
public class ClassDemo {
public static void main(String[] args) {
// 接口的子类对象作为函数的参数进行传递
new ClassDemo().run(new Inter(){
public void show(){
System.out.println("haha");
}
});
}
public void run(Inter in){
in.show();
}
}
// 接口
interface Inter{
public abstract void show();
}
关于面向对象的总结:在定义类时,不需要对外暴露的全部封装,仅对外提供访问方法。
继承和接口是多态的前提。多态应用时注意:父类引用可以访问共性内容,不能访问子类
的特有内容。接口的出现降低了耦合性并使类扩展功能更加方便。
The End