JAVA学习
来源:互联网 发布:c语言约束条件下最优解 编辑:程序博客网 时间:2024/06/05 20:18
2面向对象
面向对象的三大特征:封装、继承、多态。
封装机制是在程序中表现为把描述对象的属性及实现对象功能的方法合在一起,定义为一个程序单位,并保证外界不能任意更改其内部的属性的值,也不能任意调用其内部的功能方法。
封装的另一个特点就是为封装在一个整体内部的方法及变量规定了不同级别的可见性或者访问权限。
继承,在Java中继承只能是单继承。在java中通过接口的方式来弥补Java不支持多继承的不足。
多态:允许程序中出现重名的现象。多态分为方法的重载和对象的多态两种形式。
方法重载:在同一个类中,允许多个方法使用一个名字,但是方法的参数不同,完成了功能也不同。
对象多态:对象的多态一定是建立在继承的基础上,表现为子类对象可以与父类对象进行相互转换。并且根据其使用的子类的不同而完成不同的功能。向上转型和向下转型。
面向对象中的基本单位是类,类是一种抽象的模型,对象是根据这个模型实例化出来的一个具体的东西。
类是抽象的,对象是具体的。
2.1类与对象的定义
类是由属性和方法组成的,属性是定义类的一个个具体的信息,实际属性就是一个变量。而方法是一些操作行为。我们在定义类的时候使用关键字class。语法如下:
属性也叫做成员变量,方法也叫做成员方法。
class 类名称{
数据类型 属性(变量);
。。。。。。
public 返回值类型 方法名(参数列表){
程序语句;
return 表达式;
}
}
Demo :
定义一个Person类,里面要定义两个属性name String,age int,然后再定义一个方法tell(),可以有一个输出语句。
class Person {
String name;
int age;
public void tell() {
System.out.println("姓名:" +name + "年龄:" +age);
}
}
提问:为什么Person类在定义tell()方法的时候,没有加static?
调用形式不同,在前面我们定义方法的时候,是这样说的:在主类中定义,并且由主方法直接调用,那边定义的方法必须加static。但是现在这个tell,只能有Person类实例化之后的对象去调用,所以暂时没加。如果是由对象调用的方法,那么方法在定义的时候可以没有static,如果不是由对象来调用方法,那么才加static。
上面我们已经声明了一个类,但是现在还无法直接去使用,如果要使用,必须通过对象。那么因为类是引用数据类型,所以要产生一个对象必须按照如下的语法:
类名 对象名 = new 类名();
第一步,声明对象 类名 对象名 = null;
第二步,实例化对象 对象名 = new 类名();
注意:因为类是引用数据类型,那么只要是引用数据类型的实例化操作,就必须通过关键字new,其作用是开辟内存空间。当一个实例化的对象产生之后,我们可以按照如下的方式进行类的操作:
调用类中的属性:对象.属性
调用类中的方法:对象.方法()
Demo:
package com.huikedu;
class Person {
String name;
int age;
public void tell() {
System.out.println("姓名:" +name + ",年龄:" +age);
}
}
public class Demo1 {
public static void main(String[] args) {
//Person p = new Person();//声明并实例化对象
Person p = null;//声明对象
p = new Person();//实例化对象
p.name = "张三";
p.age = 18;
p.tell();
}
}
上面给出两种方式,这两种方式有什么区别:
从运行结果来看,没有任何区别。引入内存分析:
首先堆内存和栈内存。
堆内存:保存的是对象的真实数据,对视一个对象的属性内容。
栈内存:保存的是一块堆内存的空间地址。
要想开辟堆内存空间,就必须使用关键字new。
声明对象:
实例化对象:
为对象name属性赋值:
为对象age属性赋值:
注意:对象在使用之前必须进行实例化操作,没有实例化的对象是不能进行操作的。
上面的代码中,只声明了对象,而没有进行实例化,在运行的时候就报空指针异常。
2.2引用传递深入
Java有垃圾回收机制,当我们堆存没有被指向的时候,Java的垃圾回收机制就开始工作,自动进行垃圾回收,GC(Garbage Collection)。
开发中尽量减少垃圾的产生。
2.3封装初步
封装是面向对象的第一大特征。为什么要进行封装?
不封装的后果:
package com.huikedu;
class Person {
String name;
int age;
public void tell() {
System.out.println("姓名:" +name + ",年龄:" +age);
}
}
public class Demo1 {
public static void main(String[] args) {
Person p1 = new Person();//声明并实例化对象
p1.name = "张三";
p1.age = -18;
p1.tell();
}
}
属性不受保护,用户可以随意修改。这是具有很大的安全隐患。
接下来使用private对属性进行封装。
class Person {
private Stringname;
private int age;
public void tell() {
System.out.println("姓名:" +name + ",年龄:" +age);
}
}
就可以看到,在外部对类中的属性进行操作的时候,权限不足的情况:
在外部修改属性是经常性的操作,那么就需要针对这些属性对外暴漏一个方法,来进行属性的操作。那么我们在方法中就能做到有效的控制。必须按照如下形式来定义方法:
setter方法:(以name为例):publicvoid setName(String n)
getter方法:(以name为例):publicString getName()
修改上面的错误程序:
package com.huikedu;
class Person {
private Stringname;
private int age;
publicvoid setName(String n){
name = n;
}
publicvoid setAge(int a){
if(a>=0 && a<=200){
age = a;
}
}
public String getName(){
returnname;
}
publicint getAge(){
returnage;
}
public void tell() {
System.out.println("姓名:" +name + ",年龄:" +age);
}
}
public class Demo1 {
public static void main(String[] args) {
Person p1 = new Person();//声明并实例化对象
p1.setName("张三");
p1.setAge(18);
p1.tell();
}
}
上面的程序,我们并没有用到getter方法,但是还是协商了,为什么?因为这是开发标准,日后在开发过程中,也是要写上的。
2.4构造方法
我们前面在讲方法,甚至是方法的调用的时候,方法只要后面有一对(),就表示你调用了方法,但是有这样一个操作:
我们在实例化对象的时候:
Person p = new Person();
在实例化对象的时候,发现用到了Person()一个方法,这个方法在定义类的时候你有写么?并没有。并且这个方法的名字还比较奇怪,和类名一样。那么这个方法哪里来的?
在Java中,如果要定义构造方法,要求如下:
构造方法的方法名必须和类名保持一致;
构造方法不允许声明返回值类型;
由于对象在实例化的时候一定需要构造方法的存在,所以如果在定义类的时候没有定义构造方法,那么就会自动给我们的类增加一个无参、无返回值的构造方法供用户使用。但是如果你在一个类中已经明确的定义了一个构造方法的话,那么就不再自动生成无参构造。而如果你定义了一个含参数的构造方法,在实例化对象的时候你却调用了无参构造,那么程序就会报错。
也就是说,一个类中至少会存在一个构造方法。
默认情况下,程序中会自动添加一个如下所示的构造方法:
public Person(){}
我们现在在程序中定义个构造方法:
package com.huikedu;
class Person {
private Stringname;
private int age;
public Person(String n,int a){
setName(n);
setAge(a);
}
public void setName(String n){
name = n;
}
public void setAge(int a){
if(a>=0 && a<=200){
age = a;
}
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
public void tell() {
System.out.println("姓名:" +name + ",年龄:" +age);
}
}
public class Demo1 {
public static void main(String[] args) {
Person p1 = new Person("张三",18);//声明并实例化对象
// p1.setName("张三");
// p1.setAge(18);
p1.tell();
}
}
提示:在调用本类中的方法的时候,可以使用关键字this
public Person(String n,int a){
this.setName(n);
this.setAge(a);
}
在实际编程中,建议大家在调用本类的方法的时候,都加上关键字this。
构造方法依然是方法,那么构造方法的重载会怎样?
package com.huikedu;
class Person {
private Stringname;
private int age;
public Person(){}
public Person(String n){
this.setName(n);
}
public Person(String n,int a){
this.setName(n);
this.setAge(a);
}
public void setName(String n){
name = n;
}
public void setAge(int a){
if(a>=0 && a<=200){
age = a;
}
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
public void tell() {
System.out.println("姓名:" +name + ",年龄:" +age);
}
}
public class Demo1 {
public static void main(String[] args) {
Person p1 = new Person("张三");//声明并实例化对象
// p1.setName("张三");
// p1.setAge(18);
p1.tell();
}
}
注意:构造方法的顺序。
在一个类中对构造方法进行重载的时候,所有的重载方法按照参数的个数由少到多或者由多到少的顺序排列。当然你没有按照规范去排,程序不会出错。
2.5匿名对象
如果说一个对象没有名字,则成为匿名对象,匿名对象在执行完毕之后,直接被GC回收。
public class Demo1 {
public static void main(String[] args) {
Person p1 = new Person("张三");//声明并实例化对象
new Person("李五",45).tell();
// p1.setName("张三");
// p1.setAge(18);
p1.tell();
}
}
new Person("李五",45)
2.6简单Java类(核心)
简单Java类是现在最为重要的开发模型。
如果说现在定义一个类,这个类只包含基本的属性,setter(),getter()则称这样的类为简单Java类。对于简单Java类的开发原则有以下:
类名必须可以明确的表示出一类的定义,Person,Emp
l 类中的所有属性都必须使用private进行封装。
l 类中的所有属性都必须提供相应的getter和setter方法。
l 类中可以提供构造方法,为属性初始化,但是不管提供了多少构造方法,一定保留一个无参构造。
l 类中不允许直接使用System.out.println输出,所有的内容要返回给调用处输出。
以上四条原则必须准守。
简单Java类:
参照你的数据库设计,把数据库中的字段全部作为这个类的属性,并且全部进行private封装。类名就是你的表名。
package com.huikedu;
public class Emp {
private int empno;
private Stringename;
private Stringjob;
private double sal;
public Emp() {
}
public Emp(int empno, String ename, String job,double sal) {
this.empno = empno;
this.ename = ename;
this.job = job;
this.sal = sal;
}
public int getEmpno() {
return empno;
}
public void setEmpno(int empno) {
this.empno = empno;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public StringgetJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
public String getInfo() {
return "雇员信息:" +"\n" + "\t" +"雇员姓名:" +this.getEname() + "\n" + "\t"
+ "雇员编号:" +this.getEmpno() + "\n" + "\t" + "雇员工资:"
+ this.getSal() +"\n" + "\t" +"雇员职位:" +this.getJob();
}
}
测试类:
package com.huikedu;
public class TestEmp {
public static void main(String[] args) {
Emp e = new Emp(10001,"张三","总经理", 10);
System.out.println(e.getInfo());
}
}
提示:简单Java类一定要熟练编写。
2.7数组
2.7.1数组的定义及基本使用
在 Java 之中可以使用如下的格式进行数组的定义:
数据类型 数组名称[] = new 数据类型[长度]
数据类型 [] 数组名称 = new 数据类型[长度]
如果要使用数组可以采用“数组名称[索引]”的形式完成,而索引的操作范围:0 ~ 长度-1。如果说现在开辟了一个 3 个长度的数组,其索引的范围是:0、1、2
ArrayDemo1:声明并开辟数组
package com.huikedu;
public class ArrayDemo1 {
public static void main(String[] args) {
// 表示定义了一个int 型数组,长度为 3
int data [] =new int [3] ;
System.out.println(data[0]) ;
System.out.println(data[1]) ;
System.out.println(data[2]) ;
}
}
运行结果:
可以发现数组开辟之后其内部所对应的内容全部为数字 0,现在开辟的是 int 型的数组,而 int 型数据类型的默认值为 0,所以当数组开辟之后,里面的所有内容为当前默认数据类型的默认值。
ArrayDemo2:定义数组的内容
package com.huikedu;
public class ArrayDemo2 {
public static void main(String[] args) {
// 表示定义了一个int 型数组,长度为 3
int data [] =new int [3] ;
// 设置数组内容
data[0] = 10 ;
data[1] = 20 ;
data[2] = 30 ;
System.out.println(data[0]) ;
System.out.println(data[1]) ;
System.out.println(data[2]) ;
}
}
运行结果:
但是数组属于线性的存储结构,一般可以使用 for 循环的方式来控制脚标,但是现在最需要解决的是循环的次数,为了方便得到循环次数,可以利用“数组.length”的形式取得。
ArrayDemo3:
package com.huikedu;
public class ArrayDemo3 {
public static void main(String[] args) {
// 表示定义了一个int 型数组,长度为 3
int data[] =new int[3];
// 设置数组内容
data[0] = 10;
data[1] = 20;
data[2] = 30;
System.out.println(data.length);
for (int x = 0; x < data.length; x++) {// 利用 x进行脚标的控制
System.out.println(data[x]);
}
}
}
运行结果:
除了以上的方式之外,也可以采用分步的形式定义并实例化数组:
· 第一步:声明数组: 数据类型 数组名称 [] = null; // 数组属于引用数据类型
· 第二步:实例化数组 数组名称 = new 数据类型[长度] ;
ArrayDemo4:分步操作
package com.huikedu;
public class ArrayDemo4 {
public static void main(String[] args) {
int data [] =null ; // 声明数组
// 开辟堆内存空间用于保存数组内容
data = new int [3] ; // 实例化数组
// 设置数组内容
data[0] = 10;
data[1] = 20;
data[2] = 30;
System.out.println(data.length);
for (int x = 0; x < data.length; x++) {// 利用 x进行脚标的控制
System.out.println(data[x]);
}
}
}
运行结果:
而如果我们使用了未实例化的数组,那么一定会出现 NullPointerException。
ArrayIndexOutOfBoundsException:数组下标越界
那么在以上所给出的数组都属于数组的动态初始化形式,即:首先开辟数组空间,而后数组中的内容为对应数据类型的默认值,那么用户也可以在数组定义时直接给出具体的内容,其定义语法如下:
数组的静态初始化:
数据类型 数组名称 [] = {值,值,…}
数据类型 数组名称 [] = new 数据类型 {值,值,…}
ArrayDemo5:使用静态初始化定义数组
package com.huikedu;
public class ArrayDemo5 {
public static void main(String args[]) {
int data[] =newint[] { 10, 20, 30, 70, 80, 90 };
for (int x = 0; x < data.length; x++) {// 利用 x进行脚标的控制
System.out.println(data[x]);
}
}
}
运行结果:
对数组而言,在开发之中一定会出现,但是数组本身有一个问题,即:如果定义了 3 个长度的数组,那么其长度将无法进行修改,所以大家在此处一定要清楚数组的基本使用,与内存分配的关系。
2.7.2二维数组
之前学习过的数组里面只包含有一个“[]”,那么就可以简单的将其理解为是一维数组,只需要一个索引就可以确定数据的内容。而如果说现在有了两个“[]”那么就表示二维数组。
如果要想进行二维数组的定义可以采用以下两种格式完成:
·数组的动态初始化
数据类型 数组名称[][] = new 数据类型[行长度][列长度]
·数组的静态初始化:
数据类型 数组名称[] [] = {{值, 值,..}, {值, 值,..},…}
数据类型 数组名称[] [] = new 数据类型[][] {{值, 值,..}, {值, 值,..},…}
ArrayDemo6:使用动态初始化操作
package com.huikedu;
public class ArrayDemo6 {
public static void main(String[] args) {
// 是一个 3行 4 列的数组
int data[][] =new int[3][4];
// 设置数组内容
data[0][0] = 10;
data[1][0] = 20;
data[2][0] = 30;
data[0][2] = 60;
data[1][2] = 70;
for (int x = 0; x < data.length; x++) {
for (int y = 0; y < data[x].length; y++) {
System.out.print(data[x][y] +"\t");
}
System.out.println();
}
}
}
运行结果:
ArrayDemo7:利用静态初始化进行二维数组定义
package com.huikedu;
public class ArrayDemo7 {
public static void main(String[] args) {
// 是一个 3行 4 列的数组
int data[][] =new int[][] {
{ 1, 2, 3 }, { 6, 7, 8 }, { 10, 11, 12 } };
for (int x = 0; x < data.length; x++) {
for (int y = 0; y < data[x].length; y++) {
System.out.print(data[x][y] +"\t");
}
System.out.println();
}
}
}
运行结果:
通过二维数组的讲解,相信大家已经可以理解数组之中,由“[]”来决定数组的维度,但是从另外一个方面来讲,二维数组属于复杂数组,因为它表示的是一张表的形式,如果是三维呢?那么就表示一个立体面,自然操作就更加复杂了,但是幸的是,我们在进行 JAVA 开发之中,不需要牵扯到多维数组,基本上一维就足够使用了。
2.7.3 Java 对数组操作的支持
所谓的数组拷贝指的是可以将一个数组的部分内容拷贝到另外一个数组之中,此方法定义如下
System.arraycopy(源数组名称,源数组开始点,目标数组名称,目标数组开始点,长度)
ArrayDemo8:实现数组拷贝
· 原始数组 1: 1、2、3、4、5、6、7、8、9
· 原始数组 2: 11、22、33、44、55、66、77、88、99
要求将数组 2 的部分内容拷贝到数组 1 之中,最终数组 1 的数据为: 1、2、3、66、77、88、7、8、9
package com.huikedu;
public class ArrayDemo8 {
public static void main(String[] args) {
int dataA[] =new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int dataB[] =new int[] { 11, 22, 33, 44, 55, 66, 77, 88, 99 };
System.arraycopy(dataB, 5, dataA, 3, 3);
for (int x = 0; x < dataA.length; x++) {
System.out.println(dataA[x] +"\t");
}
}
}
运行结果:
除了数组拷贝之外,还可以利用以下的方式实现数组的排序:java.util.Arrays.sort(数组名称)。
ArrayDemo9:实现数组的排序操作
package com.huikedu;
public class ArrayDemo9 {
public static void main(String[] args) {
int data[] =new int[] { 8, 1, 3, 0, 9, 2, 6, 7 };
java.util.Arrays.sort(data);
// 排序
for (int x = 0; x < data.length; x++) {
System.out.println(data[x]);
}
}
}
运行结果:
2.7.4数组排序操作
数组排序是一种常见的数据操作,下面直接通过具体的代码进行说明。
ArrayDemo10:
package com.huikedu;
public class ArrayDemo10 {
public static void main(String args[]) {
int data[] =new int[] {7, 9, 8, 0, 1, 2, 5, 4, 3 };
for (int x = 0; x < data.length; x++) {
for (int y = 0; y < data.length - 1; y++) {
if (data[y] > data[y + 1]) {
int temp = data[y];
data[y] = data[y + 1];
data[y + 1] = temp;
}
}
print(data);
}
}
public static void print(int temp[]) {
for (int x = 0; x < temp.length; x++) {
System.out.print(temp[x] +"\t");
}
System.out.println();// 换行
}
}
运行结果:
虽然在 JDk 之中提供给用户排序的操作支持,但是在一些笔试中依然会出现让用户自己根据数组内容排序的题目,当然这个时候你是绝对不能写 java.util.Arrays.sort()的,必须编写以上的代码。
2.8String类
基本数据类型有8种,int、byte、short、double、long、float、boolean、char。没有String,String表示的字符串,不是基本数据类型,就是引用数据类型,那么引用数据类型在实例化的时候要使用关键字new。
Demo:
package com.huikedu;
public class Demo4 {
public static void main(String[] args) {
String s = "Hello";
System.out.println(s);
}
}
2.8.1String类的两种实例化方式
String类之所以特殊,其根本原因就是它有两种不同的对象实例化方式。
第一种:直接将字符串赋值为一个String类型的对象。
第二种:采用String类的构造方法为String类的对象实例化,String类的构造方法:public String(){}
采用构造方法对String进行实例化
package com.huikedu;
public class Demo4 {
public static void main(String[] args) {
String s = "Hello";
System.out.println(s);
String s1 = new String("Hello1");
System.out.println(s1);
}
}
2.8.2字符串比较
我们在讲int类型的时候,要比较两个数字的是否相等是通过比较运算符“==”。来比较两个字符串。
package com.huikedu;
public class Demo {
public static void main(String [] args){
int x = 10;
int y = 10;
System.out.println(x==y);
String s1 = "Hello";
String s2 = new String("Hello");
String s3 = s2;//引用传递
System.out.println(s1==s2);//false
System.out.println(s1==s3);//false
System.out.println(s2==s3);//true
}
}
将上述内存图画出:
public boolean equals(Object anObject)
demo:
package com.huikedu;
public class Demo {
public static void main(String [] args){
int x = 10;
int y = 10;
System.out.println(x==y);
String s1 = "Hello";
String s2 = new String("Hello");
String s3 = s2;//引用传递
System.out.println(s1.equals(s2));//true
System.out.println(s1.equals(s3));//true
System.out.println(s2.equals(s3));//true
}
}
要想进行字符串的比较,可以通过equals()方法进行比较。
2.8.3字符串常量是String类的匿名对象
package com.huikedu;
public class Demo5 {
public static void main(String[] args) {
String s = "Hello";
System.out.println("Hello".equals(s));
}
}
2.8.4String类的两种实例化方式的区别
1.分析直接赋值的情况
String s = “Hello”;
Demo:
package com.huikedu;
public class Demo6 {
public static void main(String[] args) {
String s1 = "Hello";
String s2 = "Hello";
String s3 = "Hello";
System.out.println(s1==s2);
System.out.println(s1==s3);
System.out.println(s2==s3);
}
}
在每一个运行的JVM的底层中存在一个字符串的对象池,如果用户直接采用赋值的方式,会将字符串先放入到池中,以便其他继续工直接赋值的String使用。
2分析构造方法赋值的情况
package com.huikedu;
public class Demo7 {
public static void main(String[] args) {
String s = new String("Hello");
System.out.println(s);
}
}
每个字符串都是匿名对象。
观察入池:
package com.huikedu;
public class Demo7 {
public static void main(String[] args) {
String s = new String("Hello");//不会入池
String s1 = "Hello";//入池
String s2 = "Hello";//直接使用
System.out.println(s==s1);//false
System.out.println(s==s2);//false
System.out.println(s1==s2);//true
}
}
通过上述代码知道,使用构造方法实例化String对象的时候,不会入池,那么能不能手动入池呢?
package com.huikedu;
public class Demo7 {
public static void main(String[] args) {
String s = new String("Hello").intern();//手工入池
String s1 = "Hello";//直接使用
String s2 = "Hello";//直接使用
System.out.println(s==s1);//true
System.out.println(s==s2);//true
System.out.println(s1==s2);//true
}
}
2.8.5字符串的内容一旦声明则不可改变
package com.huikedu;
public class Demo8 {
public static void main(String[] args) {
String s = "Hello";//定义字符串
s += "World";//字符串连接
s += "!!!";//字符串连接
System.out.println(s);
}
}
上面的程序会产生很多垃圾,在实际工作中,这种代码要避免。
package com.huikedu;
public class Demo8 {
public static void main(String[] args) {
String s = "Hello";//定义字符串
// s += "World";//字符串连接
// s += "!!!";//字符串连接
for(int i = 1;i<10000;i++){
s += i;
// System.out.println(s);
}
System.out.println(s);
}
}
这种代码在执行的时候会“断开-连接”10000次,会产生大量的垃圾。不能写这种代码!
2.8.6String类的常用方法
查阅API熟悉String中的各种操作方法。
2.9this关键字
This关键字表示本类的属性,还可以表示本类的方法,还可以表示当前对象。总共3种。
2.9.1表示本类的属性“this.属性”
public Emp(int empno, String ename, String job,double sal) {
this.empno = empno;
this.ename = ename;
this.job = job;
this.sal = sal;
}
2.9.2调用本类中的方法
在一个类中,现在主要就是两种方法(普通方法、构造方法),而要调用方法就分为两种形式:
第一种,调用普通方法,this.方法名()。
public Person(String n,int a){
this.setName(n);
this.setAge(a);
}
第二种:调用本类中的构造方法。在一个构造方法中调用其他构造方法使用this()去调用。
Demo:
package com.huikedu;
class Person1{
private Stringname;
private int age;
public Person1(){
System.out.println("****一个新的Person对象被实例化。这里想象成有5000行代码******");
}
public Person1(String name){
System.out.println("****一个新的Person对象被实例化。这里想象成有5000行代码******");
this.name = name;
}
public Person1(String name,int age){
System.out.println("****一个新的Person对象被实例化。这里想象成有5000行代码******");
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getInfo(){
return "name:"+this.name+"age:"+this.age;
}
}
public class Demo9 {
public static void main(String[] args) {
Person1 p = new Person1("zhangsan", 18);
System.out.println(p.getInfo());
}
}
此时不管调用哪一个构造,都可以完成指定信息的输出。上述的程序中有大量的重复代码,那么如何减少这些重复代码?
package com.huikedu;
class Person1{
private Stringname;
private int age;
public Person1(){
System.out.println("****一个新的Person对象被实例化。这里想象成有5000行代码******");
}
public Person1(String name){
this();
this.name = name;
}
public Person1(String name,int age){
this(name);
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getInfo(){
return "name:"+this.name+"age:"+this.age;
}
}
public class Demo9 {
public static void main(String[] args) {
Person1 p = new Person1("zhangsan", 18);
System.out.println(p.getInfo());
}
}
实现了构造方法的互相调用。
注意:在使用this调用构造方法的时候要注意以下问题:
所有的构造方法都是在对象实例化的时候被默认调用的,而且在调用普通方法之前调用,所以使用this()调用构造方法就必须放在首行。
构造方法之间通过this()实现相互调用,请至少留一个出口。
作业:
编写一个员工类,属性有:工号、姓名、薪水、部门。
利用构造方法完成信息的设置
1、 一个参数(工号)
2、 双参(工号、姓名)
3、 四个参数(工号、姓名、薪水、部门)
4、 无参,都为空
显示信息
Setter和getter
2.9.3this表示当前对象
package com.huikedu;
class DemoA{
public void print(){
System.out.println("当前对象:"+this);
}
}
public class Demo10 {
public static void main(String[] args) {
DemoA d1 = new DemoA();
DemoA d2 = new DemoA();
System.out.println(d1);
d1.print();
System.out.println("=====================");
System.out.println(d2);
d2.print();
}
}
运行结果:
2.10引用传递
package com.huikedu;
class Demo12{
private int data = 10;
public Demo12(int data){
this.data = data;
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
}
public class Demo11 {
public static void main(String[] args) {
Demo12 d12 = new Demo12(100);
fun(d12);
//Demo12 d = d12;
// d.setData(30);
System.out.println(d12.getData());
}
public static void fun(Demo12 d){
d.setData(30);
}
}
在主方法中实例化了一个Demo12 的对象,同时为类中的属性data赋值为100,之后将此类的对象传递给了fun(),由于类本身是引用数据类型,所以fun方法中修改直接影响到原始的对象中的数据。
Demo :
package com.huikedu;
public class Demo13 {
public static void main(String[] args) {
String s = "Hello";
fun(s);
System.out.println(s);
}
public static void fun(String temp){
temp = "world";
}
}
Demo :
package com.huikedu;
class Demo15{
private Stringdata;
public Demo15(String data){
this.data = data;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
public class Demo14 {
public static void main(String[] args) {
Demo15 d15 = new Demo15("Hello");
fun(d15);
System.out.println(d15.getData());
}
public static void fun(Demo15 temp){
temp.setData("World");
}
}
2.11比较对象
第一种方式:
package com.huikedu;
class Person16{
private String name;
private int age;
public Person16(String name,int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Demo16 {
public static void main(String[] args) {
Person16 p1 = new Person16("张三", 13);
Person16 p2 = new Person16("张三", 13);
if(p1.getName().equals(p2.getName()) && p1.getAge() == p2.getAge()){
System.out.println("是同一个对象");
}else{
System.out.println("不是同一个对象");
}
}
}
上面实现了两个对象的比较,但是借助的是第三方(主类)来进行的,但是比较不应该是自身的一个方法么?
package com.huikedu;
class Person16{
private Stringname;
private int age;
public Person16(String name,int age){
this.name = name;
this.age = age;
}
public boolean compare(Person16 p){
if(p ==null){
return false;
}
if(this == p){
return true;
}
if(this.name .equals(p.name)&&this.age == p.age){
return true;
}
return false;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Demo16 {
public static void main(String[] args) {
Person16 p1 = new Person16("张三", 13);
Person16 p2 = new Person16("张三", 13);
if(p1.compare(p2)){
System.out.println("是同一个对象");
}else{
System.out.println("不是同一个对象");
}
}
}
上面的程序是直接在Person16里面定义了比较的方法,此方法的功能就是进行比较,并且当一个类接收了本类的对象的引用之后,可以直接调用本类中的私有化操作。
本类接收自己的引用,然后与本类当前的对象进行比较
为了避免出现空指针异常,所以第一步先增加一个null的判断
为了避免自己和自己比较,所以我们先直接比较两个地址。
之后才是属性的比较,如果属性全部相同,则true,否则false
2.12static关键字
2.12.1使用static定义属性
Static定义的属性被称作为公共属性。
package com.huikedu;
class Person17{
private Stringname;
private int age;
static Stringcountry = "北京"; //公有属性
public Person17(String name,int age){
this.name = name;
this.age = age;
}
public String getInfo(){
return "name:"+this.name+" age:"+this.age+" country:"+this.country;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Demo17 {
public static void main(String[] args) {
Person17 p1 = new Person17("zhansan", 18);
Person17 p2 = new Person17("lisi", 20);
Person17 p3 = new Person17("wangwu", 78);
p1.country ="南京";
System.out.println(p1.getInfo());
System.out.println(p2.getInfo());
System.out.println(p3.getInfo());
}
}
Java中主要存在四块内存空间:
栈内存:保存所有对象的名称,堆内存的空间地址
堆内存:保存的对象的具体属性内容
全局数据区:保存static类型的属性
全局代码区:保存所有的方法定义
所以说上面的例子中当有一个对象修改了country的内容,其他就都被修改了。
既然说使用static定义的属性表示的是公共属性,那么如果现在有某一个对象去修改是不合适的。应该有所有对象的公共代表——类来进行操作。也就是说static定义的属性最好是由类名直接调用。
Person17.country = "南京";
使用static定义的属性不在堆内存中保存,而是在全局数据区
使用static定义的属性表示类属性,类属性可以由类直接进行调用
Static定义的属性,可以在没有实例化对象的时候进行调用。
切记:日后开发,更多的是普通属性,不是static属性。
2.12.2使用static定义方法
使用static定义的方法也可以在类没有被实例化的情况下直接使用类名进行调用。
关于方法调用:
现在的方法就分为了static方法和非static方法,这两类方法在调用的时候,存在一些限制:
Static定义的方法不能调用非static定义的方法或属性。
非static定义的方法可以调用static属性和方法。
使用static定义的属性和方法,可以在没有实例化对象的时候使用
非static定义的属性和方法,必须实例化对象后才能进行调用。
2.12.3理解主方法
Public static void main()
在主类中,由于方法是由主方法直接调用的,所有主类里面定义的方法都是static。
package com.huikedu;
public class Demo18 {
public static void main(String[] args) {
print();
}
public static void print(){
System.out.println("Hello");
}
}
上面实际就是静态方法调用静态方法。
如果主类中的期房方法没有static呢?
发现,主方法现在不能直接调用,也就是说任何时候非static放在被调用之前,都必须有实例化对象的产生。
package com.huikedu;
public class Demo18 {
public static void main(String[] args) {
new Demo18().print();
}
public void print(){
System.out.println("Hello");
}
}
在实际开发中,对于主类往往只是编写一个主方法,并且static的情况也不多见,大多数情况下都是定义非static方法。
Public:访问权限,表示公共
Static:此方法由类名直接调用,javaHello
Void:表示主方法是一切方法的开头,开弓没有回头箭
Main:系统规定的一个方法名,执行主类的时候自动去找这个名字
String args[]:表示的是一些运行时候的参数,通过字符串接收。
2.12.4static关键字的作用
在实际工作中,使用static关键字定义属性或方法原因有两个:
第一个:希望在没有实例化对象的时候可以轻松的执行类的某些操作。
第二个:希望表示出数据共享的概念。
统计
package com.huikedu;
class Person19 {
private static int count = 0;
public Person19() {
System.out.println("对象的个数:" + ++count);
}
}
public class Demo19 {
public static void main(String[] args) {
new Person19();
new Person19();
new Person19();
new Person19();
new Person19();
new Person19();
new Person19();
new Person19();
new Person19();
}
}
为类的属性自动命名
package com.huikedu;
class Book{
private static int count = 0;
private Stringtitle;
public Book(){
this("HAHAHAHA"+count++);
}
public Book(String title){
this.title = title;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
public class Demo20 {
public static void main(String[] args) {
System.out.println(new Book().getTitle());
System.out.println(new Book("我爱Java!").getTitle());
System.out.println(new Book().getTitle());
System.out.println(new Book().getTitle());
System.out.println(new Book().getTitle());
}
}
2.13代码块
代码块分为4种:普通代码块、构造块、静态块和同步块
package com.huikedu;
public class Demo21 {
public static void main(String[] args) {
if(true){
int x = 10;
System.out.println("x="+x);
}
int x = 100;
System.out.println("x1="+ x);
}
}
因为第一个x在if的{}中,所有相对于第2个x,第一个x就是局部变量。
什么是局部变量?什么是全局变量?
普通代码块
package com.huikedu;
public class Demo21 {
public static void main(String[] args) {
{
int x = 10;
System.out.println("x=" + x);
}
int x = 100;
System.out.println("x1=" + x);
}
}
构造块:
package com.huikedu;
class Person22{
public Person22(){
System.out.println("构造方法");
}
{
System.out.println("构造块");
}
}
public class Demo22 {
public static void main(String[] args) {
new Person22();
new Person22();
new Person22();
new Person22();
}
}
每次实例化对象都执行一次构造方法,并且会执行构造块,且构造块优先于构造方法先执行。
静态块:
静态块也是定义在类中的,如果一个构造块使用了static,那么就表示静态块。
两种考虑:
在非主类中的定义的构造块
在主类中定义的构造块
非主类:
package com.huikedu;
class Person22{
public Person22(){
System.out.println("构造方法");
}
{
System.out.println("构造块");
}
static{
System.out.println("静态块");
}
}
public class Demo22 {
public static void main(String[] args) {
new Person22();
new Person22();
new Person22();
new Person22();
}
}
静态块最先执行,并且只执行了一次
主类中的静态块
package com.huikedu;
public class Demo23 {
static{
System.out.println("静态块");
}
public static void main(String[] args) {
System.out.println("Hello");
}
}
先执行静态块,再执行主方法。
package com.huikedu;
public class Demo23 {
static{
System.out.println("静态块");
System.exit(1);
}
}
2.14继承
有这么一个现实的问题,现在有一个Person类,还有一个Student类。
Person.java
Student.java
class Person{
private Stringname;
privateintage;
public String getName() {
returnname;
}
publicvoid setName(String name) {
this.name = name;
}
publicint getAge() {
returnage;
}
publicvoid setAge(int age) {
this.age = age;
}
}
class Student{
private Stringname;
privateintage;
private Stringschool;
public String getName() {
returnname;
}
publicvoid setName(String name) {
this.name = name;
}
publicint getAge() {
returnage;
}
publicvoid setAge(int age) {
this.age = age;
}
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
}
2.14.1继承是如何实现的呢?
在Java中,要实现继承通过关键字extends来实现的。严格的来讲,所谓继承就是指扩充一个类的已有的功能。
语法结构:
class 子类 extends 父类{}
子类又称为派生类,父类又叫超类。
package com.huikedu;
class Person{
private Stringname;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
class Studentextends Person{
private Stringschool;
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
}
public class Demo1 {
public static void main(String[] args) {
Student s = new Student();
s.setName("张三");
s.setAge(18);
System.out.println("name:"+s.getName()+" age:"+s.getAge());
}
}
子类对父类功能的扩充
package com.huikedu;
class Person{
private Stringname;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
class Studentextends Person{
private Stringschool;
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
}
public class Demo1 {
public static void main(String[] args) {
Student s = new Student();
s.setName("张三");
s.setAge(18);
s.setSchool("nuaa");
System.out.println("name:"+s.getName()+" age:"+s.getAge() +" school:"+s.getSchool());
}
}
关于继承,有一些限制:
一、 一个子类只能继承一个父类,存在单继承的限制,但是在实际开发中,多层继承不建议写的太多,而是最多不超过3层。
二、 在一个子类继承时,实际上会继承父类中的所有属性和方法,但是对于多有的非私有化操作属于显示继承(s.setName("张三");),而私有化的操作属于隐式继承(间接)(比如说在上面的例子中,name和age就是私有化的,我们不能直接操作者两个属性,都属于间接操作)
三、 在继承关系中,如果说你要实例化子类的对象,默认会先调用父类的构造,为父类中的属性初始化,然后再调用子类中的构造,为子类中的属性初始化。默认情况下,子类会找到父类中的无参构造。
package com.huikedu;
class Person{
private Stringname;
private int age;
public Person(){
System.out.println("--------------父类中的无参构造--------------");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
class Studentextends Person{
public Student(){
System.out.println("++++++++++++++子类中的无参构造++++++++++++++");
}
private Stringschool;
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
}
public class Demo1 {
public static void main(String[] args) {
Student s = new Student();
s.setName("张三");
s.setAge(18);
s.setSchool("nuaa");
System.out.println("name:"+s.getName()+" age:"+s.getAge() +" school:"+s.getSchool());
}
}
运行结果:
会发现,在实例化子类的对象时,会默认先执行父类的构造,再执行子类的构造。此时对于子类的构造而言,就相当于隐含了一个super()的形式。
class Studentextends Person{
public Student(){
super();
System.out.println("++++++++++++++子类中的无参构造++++++++++++++");
}
private Stringschool;
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
}
默认情况下,子类在实例化的时候,先去调用父类的无参构造,但是现在如果父类中不存在一个无参构造,你又要实例化子类的对象,那么你就得通过super去调用父类中含有参数的构造。
package com.huikedu;
class Person{
private Stringname;
private int age;
public Person(String name,int age){
this.name = name;
this.age = age;
System.out.println("--------------父类中的两参构造--------------");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
class Studentextends Person{
public Student(String name,int age,String school){
super(name, age);
this.school = school;
System.out.println("++++++++++++++子类中的san参构造++++++++++++++");
}
private Stringschool;
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
}
public class Demo1 {
public static void main(String[] args) {
Student s = new Student("zhangsan",18,"nuaa");
// s.setName("张三");
// s.setAge(18);
// s.setSchool("nuaa");
System.out.println("name:"+s.getName()+" age:"+s.getAge() +" school:"+s.getSchool());
}
}
This和super俩不能同时出现。
我们在讲this的时候,必须留意个出口。
2.15覆写
什么是覆写?当发生继承关系的时候,子类中存在了和父类一样方法名,返回值类型,参数类型及个数完全相同的时候,称为方法的覆写。
Demo:
package com.huikedu;
class A2{
public void print(){
System.out.println("父类中的print");
}
}
class B2 extends A2{
}
public class Demo2 {
public static void main(String[] args) {
B2 b = new B2();
b.print();
}
}
上面的程序中,子类中没有print方法,所以就使用了从父类中继承过来的方法。
2.15.1方法的覆写
下面在子类中实现覆写:
package com.huikedu;
class A2{
public void print(){
System.out.println("父类中的print");
}
}
class B2 extends A2{
public void print() {
System.out.println("子类来了,父类就没用了!");
}
}
public class Demo2 {
public static void main(String[] args) {
B2 b = new B2();
b.print();
}
}
在进行方法的覆写的时候,被子类覆写的方法不能比父类拥有更严格的访问控制权限。如果父类中的方法是public,那么子类只能是public。
补充一下:包及访问控制权限
7包的访问控制权限
7.1包的定义及使用
7.1.1包的定义
包的作用主要是将不同的功能的文件进行分割,当然在前面的所有的Demo都是定义在同一个包中的,也就是在同一个文件夹中,一旦出现了文件名一致的情况,就会被覆盖。
一旦你定义了包,就意味着你对这个类文件进行了分割,在调用一个类的时候要在类名前面加上包名。
cn.jx.ycu.Demo1
7.1.2包的导入
在实际开发中,没有包的类是不存在的,那么在一个项目中,不同的包下面一定包含了不同功能的类,一定会出现类之间的相互调用,在调用之前就应该进行导包。通过关键字import来完成。
一个包中的类要想被其他包中的类调用,那么这个类就必须定义为public class,而不能使用class进行定义。因为用class定义的类的访问权限相当于是default,只能在一个包中使用,其它包中是看不到的。
7.2Java开发中常用的包
Java提供了大量的系统包,在使用的时候,可以直接导包进行类的调用,常用的包有:
Java.lang:这个包是最最最最常用的,但是不需要手工导入,它是自动导入的。
Java.lang.rflect:反射机制的包,是lang包的子包。
Java.util:工具包,常用的一些类库、日期操作。如果你精通该包,各种设计模式都不是问题。
Java.text:文本操作的类库
Java.sql:数据库操作包,提供了各种数据库的操作的类和接口。
Java.net:网络编程
Java.io:输入输出
7.3四种访问权限
范围
Private
Default
Protected
Public
同一个包中的同一个类
√
√
√
√
同一个包的不同的类
√
√
√
不同包的子类
√
√
不同包的非子类
√
Private只能在一个类中访问
Default只能在一个包中访问
Protected在不同包的子类(例子交给你们)
Public都可以访问
关于权限的使用
在实际开发中,大家只要做到:
属性声明以private为主
方法声明以public为主
经常说的封装是private、default和protected
7.4命名规范
类名:每一个单词的首字母都大写
变量名:第一个单词的首字母小写,之后的每个单词的首字母大写
方法名:第一个单词的首字母小写,之后的每个单词的首字母大写
常量名:每个字母都大写
包名:所有字母都小写
在你进入一个项目组中,如果该项目组没有规定的命名方式,就采取上面的命名原则。上面原则基本上是通用的,所有的Java程序员都必须遵守。
重载就是方法的多态之一,那么方法的覆写实际上也是多态。
当一个子类覆写了一个父类中方法时,子类想要调用父类的被覆写的方法,该怎么调用?通过使用关键字super进行调用。
package com.huikedu;
class A2{
public void print(){
System.out.println("父类中的print");
}
}
class B2 extends A2{
public void print() {
super.print();
System.out.println("子类来了,父类就没用了!");
}
}
public class Demo2 {
public static void main(String[] args) {
B2 b = new B2();
b.print();
}
}
this.方法():先从本类中查找指定的方法,如果找不见,再去父类中去查找。
Super.方法():直接由子类去调用父类中的方法,不再找子类中的。
注意:如果父类的方法为private,则子类无法覆写,实际中也不会发生这种情况。
定义方法的操作中,大多数情况是public
2.15.2属性的覆盖
当一个子类中定义了和父类重名的属性的时候,就表示属性的覆盖。
package com.huikedu;
class A3{
public Stringmsg = "nuaa";
}
class B3 extends A3{
public int msg = 100;
public void print(){
System.out.println("msg:"+this.msg);
System.out.println("msg:"+super.msg);
}
}
public class Demo3 {
public static void main(String[] args) {
B3 b = new B3();
b.print();
}
}
没有意义,实际开发中不会这样写。
面试题:请解释this和super的区别?
序号
区别
This
Super
1
定义
表示本类对象
表示父类对象
2
使用
本类操作:this.属性,this.方法,this()
父类操作:
Super.属性、super.方法
Super()
3
调用构造
调用本类的,放在首行
调用父类的,放在首行
4
查找范围
先找本类,再找父类
直接找父类
5
特殊
表示当前对象
-
面试题:请解释重载和覆写的区别?
区别
重载
覆写
单词
Overloading
Overriding
定义
方法名相同,参数不同
方法名、参数、返回值都相同
权限
没有权限要求
子类的权限必须必父类的权限大
范围
发生一个类中
发生在继承关系中
面试题:构造方法是否可以被覆写?
构造方法不能被继承,因此不能被覆写,但是可以重载。
2.16final关键字
在Java中,final关键字表示的是一个终结器的概念,使用final可以定义一个类、方法、变量。
1、 使用final定义的类,不能有子类。不能被继承。
2、 使用final定的方法不能被子类所覆写。
3、 使用final定义的变量,就表示常量,在定义时就必须赋值,并且无法修改。
final doublePAI = 3.1415926;
在实际开发中,只有全局常量在实际中使用。像类、方法被final了,自己开发的时候用的真不多,一些系统提供的类会被final。
在定义final常量的时候每个单词的每个字母都要大写。
2.17单例模式
面试题:请手写一个单例模式(构造方法私有化)
当构造方法使用private声明的时候,就表示构造方法私有化了。私有化之后,会发生什么呢?
正常写法应该是下面这样,
package com.huikedu;
class Singleton{
public void print(){
System.out.println("nuaa");
}
}
public class Demo4 {
public static void main(String[] args) {
Singleton ins = null;
ins = new Singleton();
ins.print();
}
}
现在将Singleton中的构造方法私有化,
package com.huikedu;
class Singleton{
private Singleton(){}
public void print(){
System.out.println("nuaa");
}
}
public class Demo4 {
public static void main(String[] args) {
Singleton ins = null;
// ins = new Singleton();
ins.print();
}
}
在外部不能实例化对象了,那么内部可以。我们在内部实例化一个对象出来。
package com.huikedu;
class Singleton{
Singleton instance =new Singleton();
private Singleton(){}
public void print(){
System.out.println("nuaa");
}
}
public class Demo4 {
public static void main(String[] args) {
Singleton ins = null;
// ins = new Singleton();
ins.print();
}
}
对于类中的普通属性,默认情况下一定要在本类存在了实例化对象后才可以进行调用,可是现在在Singleton类的外部无法产生实例化对象,那么我们想一个办法,让Singleton类中的instance属性可以在没有Singleton实例化对象的时候就可以进行调用,这是就可以在instance前面加上static。
package com.huikedu;
class Singleton{
static Singletoninstance = new Singleton();
private Singleton(){}
public void print(){
System.out.println("nuaa");
}
}
public class Demo4 {
public static void main(String[] args) {
Singleton ins = null;
ins = Singleton.instance;
ins.print();
}
}
我们说过,类中所有的属性都应该进行封装,所以instance属性也应该进行封装,封装之后还要去的属性的话就需要编写getter方法,只不过这时候的getter也只能由类名直接调用。
package com.huikedu;
class Singleton{
privatestatic Singleton instance =new Singleton();
private Singleton(){}
publicstatic Singleton getInstance(){
returninstance;
}
public void print(){
System.out.println("nuaa");
}
}
public class Demo4 {
public static void main(String[] args) {
Singleton ins = null;
ins = Singleton.getInstance();
ins.print();
}
}
现在这样写这个类的话,在本类里面,依然可以实例化出一个对象,对象不是唯一的。能不能实现实例化对象的唯一性呢?怎么实现?
通过关键字final修饰这个instance变量,因为final修饰的变量就变成常量了,是不能修改的。
package com.huikedu;
class Singleton{
private final static Singleton instance =new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
public void print(){
System.out.println("nuaa");
}
}
public class Demo4 {
public static void main(String[] args) {
Singleton ins = null;
ins = Singleton.getInstance();
ins.print();
}
}
上面的Singleton类至始至终只产生一个实例化的对象,这就是非常有名的单例模式。
2.18多态
多态是面向对象的最后一个特征,多态主要体现在两个方面
方法的多态性:重载和覆写。
对象的多态性:父类与子类的转换。
向上转型:子类的对象变为父类的对象,父类父类对象 = 子类实例,自动转型。
package com.huikedu;
class A5{
public void print(){
System.out.println("A5:nuaa");
}
}
class B5 extends A5{
public void print(){
System.out.println("B5:nuaa");
}
}
public class Demo5 {
public static void main(String[] args) {
A5 a = new B5();
a.print();
}
}
向下转型:父类对象变为子类对象,子类 子类对象 = (子类)父类实例,强制。
package com.huikedu;
class A5{
public void print(){
System.out.println("A5:nuaa");
}
}
class B5 extends A5{
public void print(){
System.out.println("B5:nuaa");
}
}
public class Demo5 {
public static void main(String[] args) {
A5 a = new B5();//向上转型
B5 b = (B5) a;//向下转型
b.print();
}
}
观察下面程序:
public class Demo5 {
public static void main(String[] args) {
B5 b5 = (B5) new A5();//Exception in thread "main" java.lang.ClassCastException: com.huikedu.A5 cannot be cast to com.huikedu.B5 at com.huikedu.Demo5.main(Demo5.java:21)
b5.print();
}
}
ClassCastException:类型转换异常。
对象发生向下转型之前,一定要先发生对象的向上转型。
在实际工作中,使用最多的是向上转型。如果在使用向下转型的时候,可以instanceof关键字来判断某一个对象是否为某个类的实例。
语法:
对象 instanceof 类
返回值类型是boolean
package com.huikedu;
class A5{
public void print(){
System.out.println("A5:nuaa");
}
}
class B5 extends A5{
public void print(){
System.out.println("B5:nuaa");
}
}
public class Demo5 {
public static void main(String[] args) {
A5 a = new B5();//向上转型
System.out.println(ainstanceof A5);
System.out.println(ainstanceof B5);
if(a instanceof B5){
B5 b = (B5) a;//向下转型
b.print();
}
}
}
注意:子类不要扩充方法,为了日后的维护方便,在编写代码的时候,尽量不要执行向下转型,子类尽量不扩充新的方法名称(父类中没有),应该根据父类定义的操作去完善方法。
向上转型的使用:
package com.huikedu;
class A6{
public void print(){
System.out.println("A6:nuaa");
}
}
class B6 extends A6{
public void print(){
System.out.println("B6:nuaa");
}
}
class C6 extends A6{
public void print(){
System.out.println("C6:nuaa");
}
}
public class Demo6 {
public static void main(String[] args) {
fun(new B6());
fun(new C6());
}
public static void fun(B6 b){
b.print();
}
public static void fun(C6 c){
c.print();
}
}
上面的程序实现了一个方法可以接收A类的任意子类的实例。现在A类的子类总共有89898989个子类。那我们在主方法中就得写89898989方法的重载。并且一定有新的子类产生,你就得加一个重载。利用对象的多态性去解决这个问题。
package com.huikedu;
class A6{
public void print(){
System.out.println("A6:nuaa");
}
}
class B6 extends A6{
public void print(){
System.out.println("B6:nuaa");
}
}
class C6 extends A6{
public void print(){
System.out.println("C6:nuaa");
}
}
public class Demo6 {
public static void main(String[] args) {
fun(new B6());
fun(new C6());
}
publicstaticvoid fun(A6 a){
a.print();
}
}
利用对象的向上转型实现。
2.19抽象类
普通类就是一个功能完善的类,可以直接产生对象并去使用,其中的方法都是实现完整的。抽象类的最大特点就是包含了抽象方法,抽象方法就是只声明而没有实现(没有方法体)。定义抽象类使用关键字abstract。
package com.huikedu;
abstract class Demo7 {
private Stringinfo = "nuaa";
public void print(){
System.out.println(info);
}
public abstract void get();
}
抽象类是不能不实例化的。
package com.huikedu;
public class TestDemo {
public static void main(String[] args) {
Demo7 d = newDemo7();//抽象类不能被实例化
}
}
在实际开发中抽象类的使用原则:
抽象类必须有子类,使用extends继承,一个子类只能继承一个抽象类。
子类(如果不是抽象类)必须覆写抽象类中所有的抽象方法。
抽象类对象可以使用对象的向上转型的,通过自理来实例化操作。
使用抽象类:
package com.huikedu;
abstract class Demo7 {
private Stringinfo = "nuaa";
public void print(){
System.out.println(info);
}
public abstract void get();
}
class Impl7 extends Demo7{
@Override
public void get() {
System.out.println("Hello,nuaa!");
}
}
测试类:
package com.huikedu;
public class TestDemo {
public static void main(String[] args) {
Demo7 d = new Impl7();
d.print();
d.get();
}
}
抽象类的作用主要是设计模板
模板设计模式
定义一个操作类
package com.huikedu;
abstract class Action {
public static final int EAT = 1;
public static final int SLEEP = 3;
public static final int WORK = 5;
public static final int RUN = 7;
public void order(int flag) {
switch (flag) {
case EAT:
this.eat();
break;
case SLEEP:
this.sleep();
break;
case WORK:
this.work();
break;
case RUN:
this.run();
break;
case RUN + EAT +SLEEP:
this.run();
this.eat();
this.sleep();
break;
case EAT + WORK:
this.eat();
this.work();
break;
case RUN + EAT +SLEEP + WORK:
this.run();
this.eat();
this.work();
this.sleep();
break;
}
}
public abstract void eat();
public abstract void sleep();
public abstract void run();
public abstract void work();
}
class Dog extends Action{
@Override
public void eat() {
System.out.println("dog is eating!");
}
@Override
public void sleep() {
System.out.println("dog is sleeping!");
}
@Override
public void run() {
System.out.println("dog is running!");
}
public void work(){};
}
class Robot extends Action{
@Override
public void eat() {
System.out.println("Robot is eating!");
}
@Override
public void sleep() {
// TODO Auto-generated method stub
}
@Override
public void run() {
// TODO Auto-generated method stub
}
@Override
public void work() {
System.out.println("Robot is working");
}
}
class PersonAextends Action{
@Override
public void eat() {
System.out.println("eat");
}
@Override
public void sleep() {
System.out.println("sleep");
}
@Override
public void run() {
System.out.println("run");
}
@Override
public void work() {
System.out.println("work");
}
}
测试类:
package com.huikedu;
public class TestDemo {
public static void main(String[] args) {
Action a = new Dog();
a.order(Action.EAT+Action.RUN+Action.SLEEP);
}
}
2.20接口(重点)
接口也是类,是一种特殊的类,在接口中定义的时候全部都是抽象方法和全局常量,定义接口使用的关键字是interface。
定义一个接口:
package com.huikedu;
interface Demo8 {
public static final String INFO ="nuaa";
public abstract void print();
}
在接口中,同样存在了抽象方法,但是接口对象无法直接进行对象的实例化操作,所以接口的使用原则有:
1、 每个接口必须定义子类,子类使用implements关键字实现接口。
2、 接口的子类(如果不是抽象类)必须覆写接口中所定义的全部的抽象方法。
3、 利用接口的子类,采用向上转型的方式进行接口对象的实例化操作。
子类实现接口的语法:
class 子类 [extends 父类 [implements 接口1,接口2]]
一个子类只能只能继承一个父类,但是可以实现多个接口。
package com.huikedu;
interface A9 {
public static final String INFO ="nuaa";
public abstract void print();
}
interface B9 {
public abstract void get();
}
class C9 implements A9, B9 {
@Override
public void get() {
System.out.println("get");
}
@Override
public void print() {
System.out.println("print");
}
}
public class Demo9 {
public static void main(String[] args) {
A9 a = new C9();//向上转型实现接口对象的实例化
B9 b = new C9();
a.print();
b.get();
}
}
如果说一个类既要实现接口,又要继承抽象类,那么应该先继承后实现。
package com.huikedu;
interface A9 {
public static final String INFO ="nuaa";
public abstract void print();
}
interface B9 {
public abstract void get();
}
abstract class C9{
public abstract void fun();
}
class D9 extends C9implements A9, B9 {
@Override
public void get() {
System.out.println("get");
}
@Override
public void print() {
System.out.println("print");
}
@Override
public void fun() {
System.out.println("fun");
}
}
public class Demo9 {
public static void main(String[] args) {
A9 a = new D9();
B9 b = new D9();
C9 c = new D9();
a.print();
b.get();
c.fun();
}
}
既然在定义中说,接口里面的成分要么是全局常量,要么是抽象方法。那么在定义接口的时候可不可以简写:
interface A9 {
public static [final] String INFO ="nuaa";
public [abstract] void print();
}
接口中的访问权限只有一种:public,接口中定义放的时候就算你没有加上public,最终也将是public。
在Java中,每一个抽象类都可以实现多个接口,但是一个接口却不能继承抽象类。可是接口却可以同时继承多个接口,以实现接口的多继承操作。
interface A9 {
public static String INFO = "nuaa";
public void print();
}
interface B9 {
public abstract void get();
}
interface E9 extends A9,B9{
public void e9();
}
class F9 implements E9{
@Override
public void print() {
// TODO Auto-generated method stub
}
@Override
public void get() {
// TODO Auto-generated method stub
}
@Override
public void e9() {
// TODO Auto-generated method stub
}
}
用接口定义标准
USB
package com.huikedu;
interface USB{//操作标准
public void install();
public void work();
}
class Computer{
public void plugin(USB usb){//接收USB接口实例
usb.install();
usb.work();
}
}
//定义USB设备
class Phone implements USB{
@Override
public void install() {
System.out.println("安装手机驱动程序");
}
@Override
public void work() {
System.out.println("手机与电脑连接工作");
}
}
//定义打印机
class Printerimplements USB{
@Override
public void install() {
System.out.println("安装打印机的驱动");
}
@Override
public void work() {
System.out.println("进行文件打印");
}
}
public class Demo10 {
public static void main(String[] args) {
Computer c = new Computer();
c.plugin(new Phone());
c.plugin(new Printer());
}
}
工厂设计模式
package com.huikedu;
interface Fruit{
public void eat();
}
class Apple implements Fruit{
@Override
public void eat() {
System.out.println("吃苹果");
}
}
class Banana implements Fruit{
@Override
public void eat() {
System.out.println("吃香蕉");
}
}
public class Demo11 {
public static void main(String[] args) {
Fruit f = new Apple();
f.eat();
}
}
上面的程序中存在这样一个问题,我们要去吃水果,发现调用主类,也就是借助客户端,一个客户端和一个固定的子类绑在一起。问题就是耦合。在主方法中一个接口个一个子类紧密的耦合在一起,这种方式比较直接,但是维护不方便,那么我们在中间加一个过渡。
修改:
package com.huikedu;
interface Fruit{
public void eat();
}
class Apple implements Fruit{
@Override
public void eat() {
System.out.println("吃苹果");
}
}
class Banana implements Fruit{
@Override
public void eat() {
System.out.println("吃香蕉");
}
}
class Factory{
public static Fruit getInstance(String className){
if("apple".equals(className)){
return new Apple();
}
if("banana".equals(className)){
return new Banana();
}
return null;
}
}
public class Demo11 {
public static void main(String[] args) {
Fruit f = Factory.getInstance("banana");
f.eat();
}
}
客户端不再和一个具体的子类耦合在一起,就算是以后新增加了子类,只要修改Factory里面的方法即可。
代理设计模式
package com.huikedu;
interface NetWork{
public void brower();
}
class Real implements NetWork{
@Override
public void brower() {
System.out.println("上网浏览信息");
}
}
class Proxy implements NetWork{
private NetWorknetwork;
public Proxy(NetWork network){
this.network = network;
}
public void check(){
System.out.println("检查用户是否合法");
}
@Override
public void brower() {
this.check();
this.network.brower();
}
}
public class Demo12 {
public static void main(String[] args) {
NetWork net = null;
net = new Proxy(new Real());
net.brower();
}
}
面试题:请解释抽象类与接口的区别
区别
抽象类
接口
定义关键字
abstract class
interface
组成
变量、常量、抽象方法、普通方法、构造方法
全局常量和抽象方法
权限
各种
public
关系
一个抽象类可以实现多个接口
接口不能继承抽象类,但是可以继承多个接口
使用
抽象类和接口在实例化对象的时候都是通过向上转型的方式进行实例化的。
设计模式
模板设计模式
工厂设计模式和代理设计模式
局限性
一个子类只能继承一个抽象类
一个子类可以实现多个接口
抽象类和接口在实际开发中用谁?先用谁?关系如何?
2.21Object类
在Java中,Object类是所有类的父类。现在你定义一个类默认没有继承关系的话,就继承Object类。
Class Person{}
Class Person extends Object{}
那么Object类可以接收所有类的实例化对象。
package com.huikedu;
class Person13{}
public class Demo13 {
public static void main(String[] args) {
Object o = new Person13();//向上转型
Person13 p = (Person13)o;//向下转型
}
}
除此之外,对于任意的一个简单Java类,理论上都应该覆写Object类的3个方法:
Public String toString();
Public Boolean equals(Object o);
Public int hashCode()
取得对象信息:toString()
getInfo()
package com.huikedu;
class Person13{}
public class Demo13 {
public static void main(String[] args) {
// Object o = new Person13();//向上转型
// Person13 p = (Person13)o;//向下转型
Person13 p = new Person13();
System.out.println(p);
System.out.println(p.toString());
}
}
通过本程序,我们可以明白,直接输出一个对象,与调用toString()方法后进行对象的输出结构一致。结论:对象直接输出默认调用了Object类中的toString方法。默认的toString方法有一个特点:为了适应所有的子类,在toString默认情况下输出了对象地址,当然我们可以对其进行覆写。
package com.huikedu;
class Person13{
private Stringname;
private int age;
public Person13(String name,int age){
this.name = name;
this.age = age;
}
public StringtoString(){
return "name:"+this.name+" age:"+this.age;
}
}
public class Demo13 {
public static void main(String[] args) {
// Object o = new Person13();//向上转型
// Person13 p = (Person13)o;//向下转型
Person13 p = new Person13("张三",20);
System.out.println(p);
System.out.println(p.toString());
}
}
对象比较:equals()
package com.huikedu;
class Person14{
private Stringname;
private int age;
public Person14(String name,int age){
this.name = name;
this.age = age;
}
public String toString(){
return"name:"+this.name+" age:"+this.age;
}
publicboolean equals(Object o){
if(o ==null){
returnfalse;
}
if(this == o){
returntrue;
}
if(!(oinstanceof Person14)){
returnfalse;
}
//因为name和age属性都在Person14中定义的,而Object类没有
Person14 p = (Person14)o;//向下转型,这个一定不会出异常
if(this.name.equals(p.name)&&this.age == p.age){
returntrue;
}
returnfalse;
}
}
public class Demo14 {
public static void main(String[] args) {
Person14 p1 = new Person14("zhangsan", 14);
Person14 p2 = new Person14("zhangsan", 14);
System.out.println(p1.equals(p2));
System.out.println(p1.equals("Hello"));
}
}
以上的方法才叫做真正覆写了Object的equals方法。以后如果大家在开发中要覆写equals方法的话,必须按照上面的方式进行覆写。
用Object接收所有的引用数据类型
接收数组;
package com.huikedu;
public class Demo15 {
public static void main(String[] args) {
Object o = new int[]{1,2,3};
if(o instanceof int[]){
int[] data = (int[])o;
for(int i =0;i<data.length;i++){
System.out.println(data[i]);
}
}
}
}
对于接口:
package com.huikedu;
interface Message{}
class MessageImplimplements Message{
public String toString(){
return "New Message:nuaa";
}
}
public class Demo16 {
public static void main(String[] args) {
Message msg = new MessageImpl();//向上
Object o = msg;
Message temp = (Message) o;
System.out.println(temp);
}
}
2.22基本数据类包装类
在Java中,万事万物皆对象。这个原则有一个bug,基本数据类型就不是对象,那么这个bug如何解决呢?最简单的做法就是将基本数据类型作为一个类的属性保存起来,就相当于把基本数据类型包装了一下。
Demo:
package com.huikedu;
class Int{
private int num;
public Int(int num){
this.num = num;
}
public int intValue(){
return this.num;
}
}
public class Demo17 {
public static void main(String[] args) {
Int i = new Int(10);
int result = i.intValue();
System.out.println(result * result);
}
}
上面的这个思想Java已经实现,Java提供了8种包装类,分别是:
byte:Byte、short(Short)、int(Integer)、long(Long)、float(Float)、double(Double)、boolean(Boolean)、char(Character)
又分为两大类:一类是数值型(Number的子类)byte:Byte、short(Short)、int(Integer)、long(Long)、float(Float)、double(Double),一类是对象型(Object子类)boolean(Boolean)、char(Character)
对于数值型的子类就要关注Number类里面定义的byteValue、intValue、doubleValue等这些方法,就是从包装的类中取得所包装的数值。
2.22.1装箱与拆箱
装箱:将基本数据类型变为包装类
拆箱:将包装类变为基本数据类型
Int的装箱与拆箱
package cn.jx.ycu;
public class Demo28 {
public static void main(String[] args) {
Integer var = new Integer(9);//装箱
Integer var3 = 9;//自动装箱
int result = var.intValue();//拆箱
int result3 = var3;//自动拆箱
System.out.println(result);
System.out.println(result3);
Double var1 = new Double(15.9);//装箱
double result1 = var1.doubleValue();//拆箱
System.out.println(result1);
Boolean var2 = new Boolean(true);//装箱
boolean result2 = var2.booleanValue();//拆箱
if(result2){
System.out.println("hello");
}
Boolean var4 = true;//自动装箱
if(var4){//自动拆箱后进行操作
System.out.println("hello");
}
Object obj = 9;//int-->自动装箱-->Object
int result5 = (Integer)obj;//Object-->包装类-->自动拆箱
}
}
2.22.2数据转型
包装类的最大的优点在于可以将字符串变为指定的数据类型,
Public static int parseInt(String s)
Public static double parseDoule( String s)
Public static Boolean parseBoolean(Strings)
将字符串变为整型
package cn.jx.ycu;
public class Demo29 {
public static void main(String[] args) {
String s = "17";
int var = Integer.parseInt(s);
System.out.println(var*var);
String s1 = "365.5";
double var1 = Double.parseDouble(s1);
System.out.println(var1*var1);
String s2 = "hello";
boolean var2 = Boolean.parseBoolean(s2);
if(var2){
System.out.println("Hello");
}
}
}
以上是将字符串变为指定的基本数据类型,其他类型可以查阅API。
基本数据类型如何转化成字符串?
方法一:任何基本数据类型碰见String都自动转化为String
方法二:利用String的 public static String valueOf(数据类型 变量)
Demo:
package cn.jx.ycu;
public class Demo30 {
public static void main(String[] args) {
int num = 100;
String s = String.valueOf(num);
System.out.println(s.length());
}
}
- 学习JAVA
- Java学习
- JAVA学习
- 学习Java
- java学习
- Java学习?
- Java学习
- JAVA学习
- JAVA学习
- java学习
- 学习java
- JAVA学习
- JAVA学习
- java学习
- 学习java
- 学习JAVA
- 学习JAVA
- java学习
- JavaScript 作用域链与闭包
- JZOJ 1301. treecut
- redis快速入门
- LintCode python 小白3-三角形计数
- Log4J使用配置
- JAVA学习
- poj 2065 SETI 高斯消元解模线性方程
- spring框架的作用
- hdu 2602 01背包入门
- JAVA获取定位
- mscomm用法
- Error:Execution failed for task ':app:processDebugManifest'. > Manifest merger failed with multiple
- MySQL基础(10)——字符集和语言、安全管理、数据库维护
- Codeforces Round #258 (Div. 2) D. Count Good Substrings —— 组合数学