对象是根据类创建的。在Java中,使用关键字new来创建一个新的对象。创建对象需要以下三步:
在面向对象程式设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部份包装、隐藏起来的方法。
封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。
要访问该类的代码和数据,必须通过严格的接口控制。
封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。
适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。
public class Person {
private String name;
private int age;
}
这段代码中,将 name 和 age 属性设置为私有的,只能本类才能访问,其他类都访问不了,如此就对信息进行了隐藏。
public class Person{
private String name;
private int age;
public int getAge(){
return age;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age = age;
}
public void setName(String name){
this.name = name;
}
}
采用 this 关键字是为了解决实例变量(private String name)和局部变量(setName(String name)中的name变量)之间发生的同名的冲突。
继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
Java 支撑 单继承,多重继承,不同类继承同一个父类
但不支撑 多继承
子类拥有父类非 private 的属性、方法。
子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
子类可以用自己的方式实现父类的方法。
Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 A 类继承 B 类,B 类继承 C 类,所以按照关系就是 C 类是 B 类的父类,B 类是 A 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。
继承可以使用 extends 和 implements 这两个关键字来实现继承,而且所有的类都是继承于 java.lang.Object,当一个类没有继承的两个关键字,则默认继承object(这个类在 java.lang 包中,所以不需要 import)祖先类。
extends关键字
在 Java 中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类,所以 extends 只能继承一个类。
implements关键字
使用 implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。
public interface A {
public void eat();
public void sleep();
}
public interface B {
public void show();
}
public class C implements A,B {
}
super 与 this 关键字
super关键字:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。
this关键字:指向自己的引用。
final关键字
final 关键字声明类可以把类定义为不能继承的,即最终类;或者用于修饰方法,该方法不能被子类重写。
子类是不继承父类的构造器(构造方法或者构造函数)的,它只是调用(隐式或显式)。如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。
如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。
写在下面
重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常(异常的父类啊)。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常。
在面向对象原则里,重写意味着可以重写任何现有方法。
class Animal{
public void move(){
System.out.println("动物可以移动");
}
}
class Dog extends Animal{
public void move(){
System.out.println("狗可以跑和走");
}
}
public class TestDog{
public static void main(String args[]){
Animal a = new Animal(); // Animal 对象
Animal b = new Dog(); // Dog 对象
a.move();// 执行 Animal 类的方法
b.move();//执行 Dog 类的方法
}
}
//编译运行结果
动物可以移动
狗可以跑和走
在上面的例子中可以看到,尽管b属于Animal类型,但是它运行的是Dog类的move方法。
这是由于在编译阶段,只是检查参数的引用类型。
然而在运行时,Java虚拟机(JVM)指定对象的类型并且运行该对象的方法。
因此在上面的例子中,之所以能编译成功,是因为Animal类中存在move方法,然而运行时,运行的是特定对象的方法。
引用数据类型的强制类型转换
在java中强制类型转换分为基本数据类型和引用数据类型两种,这里我们讨论的后者,也就是引用数据类型的强制类型转换。
在Java中由于继承和向上转型,子类可以非常自然地转换成父类,但是父类转换成子类则需要强制转换。因为子类拥有比父类更多的属性、更强的功能,所以父类转换为子类需要强制。那么,是不是只要是父类转换为子类就会成功呢?其实不然,他们之间的强制类型转换是有条件的。
当我们用一个类型的构造器构造出一个对象时,这个对象的类型就已经确定的,也就说它的本质是不会再发生变化了。在Java中我们可以通过继承、向上转型的关系使用父类类型来引用它,这个时候我们是使用功能较弱的类型引用功能较强的对象,这是可行的。但是将功能较弱的类型强制转功能较强的对象时,就不一定可以行了。
举个例子来说明。比如系统中存在Father、Son两个对象。首先我们先构造一个Son对象,然后用一个Father类型变量引用它:
Father father = new Son();
在这里Son 对象实例被向上转型为father了,但是请注意这个Son对象实例在内存中的本质还是Son类型的,只不过它的能力临时被消弱了而已,如果我们想变强怎么办?将其对象类型还原!
Son son = (Son)father;
这条语句是可行的,其实father引用仍然是Father类型的,只不过是将它的能力加强了,将其加强后转交给son引用了,Son对象实例在son的变量的引用下,恢复真身,可以使用全部功能了。
前面提到父类强制转换成子类并不是总是成功,那么在什么情况下它会失效呢?
当引用类型的真实身份是父类本身的类型时,强制类型转换就会产生错误。例如:
Father father = new Father();
Son son = (Son) father;
这个系统会抛出ClassCastException异常信息。
所以编译器在编译时只会检查类型之间是否存在继承关系,有则通过;而在运行时就会检查它的真实类型,是则通过,否则抛出ClassCastException异常。
所以在继承中,子类可以自动转型为父类,但是父类强制转换为子类时只有当引用类型真正的身份为子类时才会强制转换成功,否则失败。
子类向上转型为父类,只不过是披着一件父类的外衣,其实质还是子类,也就是他还是“拥有”子类的属性和方法,只不过通过父类的引用是不能访问的,原因就在于父类引用看不到子类特有的属性和方法,当强制转换为子类时,就可以见到子类庐山真面目。
好吧。我来告诉你。可以。前提是:此父类对象为子类对象强转的结果 例如:
Father father = (Father)son;
当这种情况时,可以用instanceof判断是否是子类类型(实际) 然后强转回去
if(father instanceof Son)
Son son =(Son)father;
除此之外,不行。
一个对象可以理解为一个房子,而一个类只是规定了什么地方要放什么东西,比如客厅要放沙发,餐厅要有餐桌
创建一个新对象,就是建造一栋新房子,如果你不去初始化对象,那么这个房子就是空的,没有任何装饰家具,你也不能用它来做任何有意义的事(当然这只是个比喻,空房子不能说没有用)
当调用构造函数时,才会真正让这个对象有意义,那就是布置这个房子,布置这个房子有很多设计师,子类和父类就是两个设计师,他们之间的关系可以理解为父类设计师是子类设计师的指导,首先子类会让父类先来,父类说他要A B C D E等等,于是就叫人搬来这些东西放在房子里,父类布置好了,子类就来布置了,子类就会继续在房子里添置家具等等,有时候父类和子类会有冲突,比如父类说瓷砖地板好,子类说木地板好,这时候以子类为准,因为最终这个房子是给子类的,父类只是一个顾问指导而已,这就相当于方法重写。
所以如果你说创建一个子类对象会不会同时也创建了父类对象,那答案肯定是没有
说有的如果他的意思是这个子类对象在某个时刻完全是一个父类对象,因为那个时刻他具备了父类对象所有的特征,但不具备子类的特征,那还是可以接受的,但这个过程只会创建一个对象,如果说有的认为创建了两个或者更多的对象,那肯定是错误的
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
最常用的地方就是构造器的重载。
区别点 | 重载方法 | 重写方法 |
---|---|---|
参数列表 | 必须修改 | 一定不能修改 |
返回类型 | 可以修改 | 一定不能修改 |
异常 | 可以修改 | 可以减少或删除,一定不能抛出新的或者更广的异常 |
访问修饰符 | 可以修改 | 一定不能做更严格的限制(可以降低限制) |
方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。
方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。
方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。
方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
多态是同一个行为具有多个不同表现形式或形态的能力。
多态就是同一个接口,使用不同的实例而执行不同操作.
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。
多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。
虚函数的存在是为了多态。
Java 中其实没有虚函数的概念,它的普通函数就相当于 C++ 的虚函数,动态绑定是Java的默认行为。如果 Java 中不希望某个函数具有虚函数特性,可以加上 final 关键字变成非虚函数。
我们将介绍在 Java 中,当设计类时,被重写的方法的行为怎样影响多态性。
我们已经讨论了方法的重写,也就是子类能够重写父类的方法。
当子类对象调用重写的方法时,调用的是子类的方法,而不是父类中被重写的方法。
要想调用父类中被重写的方法,则必须使用关键字 super
上面有
下面写
下面写
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。
父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。
在Java中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。
如果你想设计这样一个类,该类包含一个特别的成员方法,该方法的具体实现由它的子类确定,那么你可以在父类中声明该方法为抽象方法。
Abstract 关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。
抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。
public abstract class Employee {
private String name;
private String address;
private int number;
public abstract double computePay();
//其余代码
}
声明抽象方法会造成以下两个结果:
继承抽象方法的子类必须重写该方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该抽象方法,否则,从最初的父类到最终的子类都不能用来实例化对象。
接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。
接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。
注:JDK 1.8 以后,接口里可以有静态方法和方法体了。
语法格式如下:
[可见度] interface 接口名称 [extends 其他的接口名] {
// 声明变量
// 抽象方法
}
Interface关键字用来声明一个接口。下面是接口声明的一个简单例子。
import java.lang.*;
//引入包
public interface NameOfInterface {
//任何类型 final, static 字段
//抽象方法
}
接口有以下特性:
当类实现接口的时候,类要实现接口中所有的方法。否则,类必须声明为抽象的类。
类使用implements关键字实现接口。在类声明中,Implements关键字放在class声明后面。
实现一个接口的语法,可以使用这个公式:
...implements 接口名称[, 其他接口名称, 其他接口名称..., ...] ...
重写接口中声明的方法时,需要注意以下规则:
在实现接口的时候,也要注意一些规则:
一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用extends关键字,子接口继承父接口的方法。
接口的多继承
在Java中,类的多继承是不合法,但接口允许多继承。
在接口的多继承中extends关键字只需要使用一次,在其后跟着继承接口。 如下所示:
public interface Hockey extends Sports, Event
以上的程序片段是合法定义的子接口,与类不同的是,接口允许多继承,而 Sports及 Event 可能定义或是继承相同的方法
最常用的继承接口是没有包含任何方法的接口。
标记接口是没有任何方法和属性的接口.它仅仅表明它的类属于一个特定的类型,供其他代码来测试允许做一些事情。
标记接口作用:简单形象的说就是给某个对象打个标(盖个戳),使对象拥有某个或某些特权。
例如:java.awt.event 包中的 MouseListener 接口继承的 java.util.EventListener 接口定义如下:
package java.util;
public interface EventListener
{}
没有任何方法的接口被称为标记接口。标记接口主要用于以下两种目的:
建立一个公共的父接口:
正如EventListener接口,这是由几十个其他接口扩展的Java API,你可以使用一个标记接口来建立一组接口的父接口。例如:当一个接口继承了EventListener接口,Java虚拟机(JVM)就知道该接口将要被用于一个事件的代理方案。
向一个类添加数据类型:
这种情况是标记接口最初的目的,实现标记接口的类不需要定义任何接口方法(因为标记接口根本就没有方法),但是该类通过多态性变成一个接口类型。
为了更好地组织类,Java 提供了包机制,用于区别类名的命名空间。
Java 使用包(package)这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class)、接口、枚举(enumerations)和注释(annotation)等。
包语句的语法格式为:
package pkg1[.pkg2[.pkg3…]];
例如,一个Something.java 文件它的内容
package net.java.util;
public class Something{
...
}
那么它的路径应该是 net/java/util/Something.java 这样保存的。 package(包) 的作用是把不同的 java 程序分类保存,更方便的被其他 java 程序调用。
一个包(package)可以定义为一组相互联系的类型(类、接口、枚举和注释),为这些类型提供访问保护和命名空间管理的功能。
以下是一些 Java 中的包:
java.lang-打包基础的类
java.io-包含输入输出功能的函数
开发者可以自己把一组类和接口等打包,并定义自己的包。而且在实际开发中这样做是值得提倡的,当你自己完成类的实现之后,将相关的类分组,可以让其他的编程者更容易地确定哪些类、接口、枚举和注释等是相关的。
由于包创建了新的命名空间(namespace),所以不会跟其他包中的任何名字产生命名冲突。使用包这种机制,更容易实现访问控制,并且让定位相关类更加简单。
创建包的时候,你需要为这个包取一个合适的名字。之后,如果其他的一个源文件包含了这个包提供的类、接口、枚举或者注释类型的时候,都必须将这个包的声明放在这个源文件的开头。
包声明应该在源文件的第一行,每个源文件只能有一个包声明,这个文件中的每个类型都应用于它。
如果一个源文件中没有使用包声明,那么其中的类,函数,枚举,注释等将被放在一个无名的包(unnamed package)中。
为了能够使用某一个包的成员,我们需要在 Java 程序中明确导入该包。使用 “import” 语句可完成此功能。
在 java 源文件中 import 语句应位于 package 语句之后,所有类的定义之前,可以没有,也可以有多条,其语法格式为:
import package1[.package2…].(classname|*);
如果在一个包中,一个类想要使用本包中的另一个类,那么该包名可以省略。
类放在包中会有两种主要的结果:
下面是管理你自己 java 中文件的一种简单方式:
将类、接口等类型的源码放在一个文本中,这个文件的名字就是这个类型的名字,并以.java作为扩展名。例如:
// 文件名 : Car.java
package vehicle;
public class Car {
// 类实现
}
接下来,把源文件放在一个目录中,这个目录要对应类所在包的名字。
…\vehicle\Car.java
现在,正确的类名和路径将会是如下样子:
通常,一个公司使用它互联网域名的颠倒形式来作为它的包名.例如:互联网域名是 nowcoder.com,所有的包名都以 com.nowcoder 开头。包名中的每一个部分对应一个子目录。
例如:有一个 com.nowcoder.test 的包,这个包包含一个叫做 Nowcoder.java 的源文件,那么相应的,应该有如下面的一连串子目录:
....\com\nowcoder\test\Nowcoder.java
编译的时候,编译器为包中定义的每个类、接口等类型各创建一个不同的输出文件,输出文件的名字就是这个类型的名字,并加上 .class 作为扩展后缀。 例如:
// 文件名: Nowcoder.java
package com.nowcoder.test;
public class Nowcoder {
}
class Google {
}
现在,我们用-d选项来编译这个文件,如下:
$javac -d . Nowcoder.java
这样会像下面这样放置编译了的文件:
.\com\nowcoder\test\Nowcoder.class
.\com\nowcoder\test\Google.class
你可以像下面这样来导入所有 \com\nowcoder\test\ 中定义的类、接口等:
import com.nowcoder.test.*;
编译之后的 .class 文件应该和 .java 源文件一样,它们放置的目录应该跟包的名字对应起来。但是,并不要求 .class 文件的路径跟相应的 .java 的路径一样。你可以分开来安排源码和类的目录。
<path-one>\sources\com\nowcoder\test\Nowcoder.java
<path-two>\classes\com\nowcoder\test\Google.class
这样,你可以将你的类目录分享给其他的编程人员,而不用透露自己的源码。用这种方法管理源码和类文件可以让编译器和java 虚拟机(JVM)可以找到你程序中使用的所有类型。
类目录的绝对路径叫做 class path。设置在系统变量 CLASSPATH 中。编译器和 java 虚拟机通过将 package 名字加到 class path 后来构造 .class 文件的路径。
\classes 是 class path,package 名字是 com.nowcoder.test,而编译器和 JVM 会在 \classes\com\nowcoder\test 中找 .class 文件。
一个 class path 可能会包含好几个路径,多路径应该用分隔符分开。默认情况下,编译器和 JVM 查找当前目录。JAR 文件按包含 Java 平台相关的类,所以他们的目录默认放在了 class path 中。
用下面的命令显示当前的CLASSPATH变量:
Windows 平台(DOS 命令行下):C:> set CLASSPATH
UNIX 平台(Bourne shell 下):# echo $CLASSPATH
删除当前CLASSPATH变量内容:
Windows 平台(DOS 命令行下):C:> set CLASSPATH=
UNIX 平台(Bourne shell 下):# unset CLASSPATH; export CLASSPATH
设置CLASSPATH变量:
Windows 平台(DOS 命令行下): C:> set CLASSPATH=C:\users\jack\java\classes
UNIX 平台(Bourne shell 下):# CLASSPATH=/home/jack/java/classes; export CLASSPAT
包重名类的简单运行机制:
1 如果没有导入任何包,主函数运行时创建类的实例首先从自己所在的包里面找
2 如果导入一个包,并且导入该包的形式精确到重名的类,则主函数创建重名类实例将会从导入的包里找
3 如果导入一个包,包的格式为 import 包名.*;类型的,则主函数创建重名类实例时首先在主函数自己所在的包里找,找不到了再去导入的.包里面找
4 如果导入多个包,并且导入的多个包中只有一个导入类型精确到重名类,则主函数创建重名类实例时在导入精确到类的包里面找。
5 如果导入多个包,并且读入的多个包中都是(import 包名.;)类型的,主函数创建重名类实例时直接在自己所在包里找;如果找不到,并且导入的多个包里有两个同名类就会报错 ;
————————————————
版权声明:本文为CSDN博主「钟眞龙」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/clock_zhong_zhen/article/details/74891591