Java编程思想读书笔记
String/StringBuilder/StringBuffer
https://javapapers.com/java/java-string-vs-stringbuilder-vs-stringbuffer-concatenation-performance-micro-benchmark/
StringBuilder.append 最快
String + 次之,编译后实际为StringBuilder.append,少于1000次迭代几乎与StringBuilder.append相似。随着迭代计数的增加,性能差距随着StringBuilder的增加而略有扩大。
StringBuffer.append 第3,因为它是线程安全的追加方法的同步,所以比StringBuilder慢。
String.concat 最差
修饰符
访问权限 类 包 子类 其他包
public ∨ ∨ ∨ ∨
protected ∨ ∨ ∨ × (在其它包中的子类可以访问)
default ∨ ∨ × × (包访问权限)
private ∨ × × ×
equals和==的区别
https://www.cnblogs.com/williamjie/p/9109664.html
== 用来判断两个对象指向的内存地址是否相同。
equals用来比较的是两个对象的内容是否相等,由于所有的类都是继承自java.lang.Object类的,所以适用于所有对象,StringInteger的equals方法都重写了,如果没有对该方法进行覆盖的话,调用的仍然是Object类中的方法,而Object中的equals方法返回的却是==的判断。
String a = new String("ab"); // a 为一个引用
String b = new String("ab"); // b为另一个新对象的引用,对象的内容一样
String aa = "ab"; // 放在常量池中
String bb = "ab"; // 从常量池中查找
(a == b) // false,非同一对象
(aa == bb) // true,同一对象
也可以这么理解: String str = "hello"; 先在内存中找是不是有"hello"这个对象,如果有,就让str指向那个"hello".如果内存里没有"hello",就创建一个新的对象保存"hello". String str=new String ("hello") 就是不管内存里是不是已经有"hello"这个对象,都新建一个对象保存"hello"。
这个规则同样也适用于Integer。
Integer str1 = 10;
Integer str2 = 10;
Integer str3 = new Integer(10);
System.out.println(str1==str2);
System.out.println(str1==str3);
str1==str2 //true
str1==str3 //false
//java5以后,为提高性能,对直接赋值的Integer进行了缓存(值为 -128 到 +127,超出这个范围不缓存)
Integer str1 = 128;
Integer str2 = 128;
str1==str2 //false
HashCode
哈希码代表对象的特征。
例如对象 String str1 = “aa”, str1.hashCode= 3104
String str2 = “bb”, str2.hashCode= 3106
String str3 = “aa”, str3.hashCode= 3104
根据HashCode由此可得出str1!=str2,str1==str3
下面给出几个常用的哈希码的算法。
1:Object类的hashCode.返回对象的内存地址经过处理后的结构,由于每个对象的内存地址都不一样,所以哈希码也不一样。
2:String类的hashCode.根据String类包含的字符串的内容,根据一种特殊算法返回哈希码,只要对象的值相同则返回的哈希码也相同。
3:Integer类,返回的哈希码就是Integer对象里所包含的那个整数的数值,例如Integer i1=new Integer(100),i1.hashCode的值就是100 。由此可见,2个一样大小的Integer对象,返回的哈希码也一样。
数组初始化
声明数组:在类型后面加[] 或在数组名后面加[]
int[] a;//推荐
int b[];
声明数组时不能指定数组的长度大小,声明时只是拥有了一个对数组的引用,但数组占用的内存空间并未申请。当数组初始化时(这时必须可以确认数组长度)才会申请内存空间。
数组初始化后,长度和值就可以确定,就算没给值,因为有数组类型,值也就确定了,就是该类型的默认值。
数组初始化后还可以再次进行初始化,可以认为数组引用的内存地址没变,只是给所有下标都重新赋值了,并且增加或减少了数组原来占用的内存空间的大小。
int[3] s; //报错,声明数组时不能指定数组的长度大小
下面两例都是错的,因为初始化数组时,必须能确定数组长度(长度定了,值就可以确定,就算没给值,因为有数组类型,值也就确定了,就是该类型的默认值。)
//例1
int[] a = new int[]; //报错,因为未找定长度,这里指定0也是可以的
//例2
int[] a;
a = new int[]; //报错,因为未找定长度,这里指定0也是可以的
正确写法是
//例1
int[] a = new int[2]; //a = [0, 0]
a = new int[3]; //a = [0, 0, 0] 可以多次初始化
//例2
int[] a;
a = new int[2]; //a = [0, 0]
a = new int[3]; //a = [0, 0, 0] 可以多次初始化
String[] a = new String[3]; //a = [null, null, null]
a[2] = "a"; //a = [null, null, a]
还有一种特殊的初始化方式,就是使用{},它必须在声明创建数组时就立即赋值使用,如果先进行声明,然后再使用{}赋值是不行的。
int[] a = {1,2}; //这是简写形式,等同于 int[] a = new int[] {1,2};
a = {3,4}; //{}必须在声明创建数组时就立即赋值使用,不能先声明后再使用
a[0] = 4; //a={4,2} 给其中一个下标赋值
a[2] = 4; //报错,下标越界了,因为数组长度为2
final
http://www.importnew.com/7553.html
http://www.cnblogs.com/twoheads/p/9548821.html
http://www.importnew.com/22386.html
static
静态成员变量或静态方式可以被类调用也可以被类对象调用,即使用创建两个对象,但静态成员始终是一份,静态成员属于类本身,不属于某个new的对象。
class StaticTest{
static int i = 3;
}
StaticTest staticTest1 = new StaticTest();
StaticTest staticTest2 = new StaticTest();
staticTest1.i++; //i=4
staticTest2.i++; //i=5
StaticTest.i++; //i=6
System.out.println(StaticTest.i); //6,类调用 和 类对象调用 都是调用的同一个i
static int i = 3;
相当于public static int i = 3;
被private static修饰的属性 只能被本类中的方法(可以是非静态的方法) 调用
//被静态方法调用
class StaticTest{
private static int i = 3;
public static int getI() {
return i;
}
public static void setI(int i) {
StaticTest.i = i;
}
}
StaticTest staticTest1 = new StaticTest();
staticTest1.setI(4);
System.out.println(StaticTest.getI()); //4
StaticTest.setI(5);
System.out.println(staticTest1.getI()); //5
//被非静态方法调用,这时只能用对象调用非静态方法
class StaticTest{
private static int i = 3;
public int getI() {
return i;
}
public void setI(int i) {
StaticTest.i = i; //也可以是 this.i = i
}
}
StaticTest staticTest1 = new StaticTest();
staticTest1.setI(staticTest1.getI()+1);
StaticTest staticTest2 = new StaticTest();
System.out.println(staticTest2.getI()); //4
自增自减运算符
应该是:运算符在前时,先变值,运算符在后时后变值。这块书上说的不对。
int i = 1;
System.out.println(i++); //1
System.out.println(i); //2
System.out.println(++i); //3
System.out.println(i); //3
构造函数
构造函数不能有返回值,返回类型为void也不行。如果加上返回值类型或void,系统就不把它作为构造函数看待了(创建对象不会自动调用了),就成了普通函数。
如果没有构造函数,实例化时系统会隐形创建一个修饰符为public的无参构造函数。
构造函数只能通过构造函数调用或创建对象时自动调用,在其它普通方法内不能调用构造方法。
在一个构造函数内部可以通过this(参数)调用另一个构造函数,只能调用一次构造函数。
class abc{
abc(int a){
System.out.println(a);
}
abc(String a) {
this(1);
this(2); //第二次调用在编译时报错,提示this()调用必须函数内的第一个语句,即只能调用一次构造函数。
}
}
new abc("a");
当创建对象时:
首先 碰到new关键字后先申请内存空间
然后 初始化类内部的静态成员变量及静态块(按代码书写顺序依次初始化)
然后 初始化类内部的非静态成员变量及非静态块(按代码书写顺序依次初始化)
然后 隐形的调用类的无参构造函数(对象并不是由构造函数创建的,执行构造器的作用主要还是为了初始化变量的值)
创建对象完成
构造函数修饰符
如果声明了构造函数,修饰符有4个:1.public 2.private 3.protected 4.缺省
1.public
在任意地方均可创建对象,(声明public修饰的构造函数 相当于 没有手动声明,系统自动隐性创建的构造函数)。
2.private
只能在本类内部创建对象,类外部均不能创建对象,这类情况一般是通过静态方法返回一个对象(一般是单例),不能被子类继承
public class Outer {
private Outer(){}
private static Outer outer;
public static Outer intance() {
if (outer == null) {
outer = new Outer();
}
return outer;
}
}
Outer outer = Outer.intance();
3.protected
只能在本类及同包名下其它类中创建对象,不同包下的子类可以实例化
4.缺省修饰符
只能在本类及同包名下其它类中创建对象,不同包下的子类不可以实例化,同包下的子类可以实例化
私有构造函数作用
其他类不能从这个类派生或者创建类的实例。返回一个类的单例
方法重载
根据参数类型的顺序来区分。即每个重载的方法参数类型顺序都是唯一的。不能以返回值区分方法的重载。
基本类型能从一个较小的类型提升到一个较大的类型
abc(int a) {System.out.println("int");}
abc(long a) {System.out.println("long");}
abc(Integer a) {System.out.println("Integer");}
abc(1);
执行优先顺序为 abc(int a) > abc(long a) > abc(Integer a)。
如果abc(int a)不存在则执行abc(long a),如果abc(long a)不存在则执行abc(Integer a)。
对象类型也可以转成基本类型
abc(Integer a) {System.out.println("Integer");}
abc(int a) {System.out.println("int");}
abc(long a) {System.out.println("long");}
new abc(Integer.valueOf(1));
执行优先顺序为 abc(Integer a) > abc(int a) > abc(long a)。
如果abc(Integer a)不存在则执行abc(int a),如果abc(int a)不存在则执行abc(long a)。
成员初始化
类的成员变量如果定义时没有赋值,则java会自动给一个默认值,但对于方法内的变量定义时必须给赋值,否则编译出错。
构造器初始化
创建一个对象时,首先是类内有成员逐个完全赋值初始化,然后再调用构造函数,也就是说类内的成变员量初始化要早于构造函数的执行,即使成员变量声明定义代码写在了构造函数或其它函数后,成员变量初始化还是会早于构造函数。
成员变量之间的初始化顺序:定义成员变量的顺序决定了初始化的顺序,即定义越早初始化越早。
class a{
int i;
a(){
}
String y;
}
当new a对象时,先初始化i=0,y=null,再执行构造函数。
静态数据的初始化
无论创建多少个对象,静态数据都只占用一份存储区域,static 关键字不能应用于局部变量,因此它只作用于域。如果域是静态的基本类型域,且也没有对它进行初始化,那么它就会获得基本类型的标准值;如果是一个对象的引用,默认初始值为Null。
当第一次使用类时(不一定非要new这个对象时,使用类名直接调用静态变量也可以),静态成员按定义顺序依次全部初始化。
class Bowl{
Bowl(int marker){
System.out.println("Bowl("+marker+")");
}
}
class Table{
static Bowl bowl1=new Bowl(1);
static Bowl bowl2= new Bowl(2);
}
//开始调用
Bowl bowl = Table.bowl2;
Bowl bow2 = Table.bowl2;
//输出
Bowl(1)
Bowl(2)
执行顺序为:
1. 执行Bowl bowl = Table.bowl2;
前 先按声明顺序依次初始化所有静态成员变量。依次输出Bowl(1) Bowl(2)
2. 执行Bowl bowl = Table.bowl2;
,Table.bowl2是静态成员,只初始化一次,这里不会再次初始化,只赋值就行了。
3. 执行Bowl bow2 = Table.bowl2;
,Table.bowl2是静态成员,只初始化一次,这里不会再次初始化,只赋值就行了。
class Bowl{
Bowl(int marker){
System.out.println("Bowl("+marker+")");
}
}
class Table{
int i;
static int b;
public int getI(){
return i;
}
static Bowl bowl1=new Bowl(1);
Table(){
System.out.println(i);
i = 10;
}
static Bowl bowl2= new Bowl(2);
static {
b = 20;
System.out.println(b);
}
}
//开始调用
System.out.print(new Table().getI());
//输出
Bowl(1)
Bowl(2)
20
0
10
1. 在new Table()
之前发现静态成员bowl1,bowl2没有初始化,所以选始化bowl1、bowl2,再执行静态块static。所以输出Bowl(1),Bowl(2),20
2. 创建对象new Table()
,先初始化成员变量i=0,再调用构造函数,输出0
3. 输出getI()方法的结果 10
除了静态块,还有非静态块,非静态块会在每次new一个新对象时执行一次,且执行时间早于构造函数
class Table{
Table(){
System.out.println("Table");
}
{
System.out.println("{}");
}
}
//调用
new Table();
new Table();
输出
{}
Table
{}
Table
所以 静态成员变量(包括静态块)初始化、 非静态成员变量(包括非静态块)初始化 、 构造函数 它们的执行顺序为: 静态成员变量初始化 > 非静态成员变量初始化 > 构造函数
class Bowl{
Bowl(int marker){
System.out.println("Bowl("+marker+")");
}
void f1(int marker){
System.out.println("f1("+marker+")");
}
}
class Table{
static Bowl bowl1=new Bowl(1);
Table(){
System.out.println("Table()");
bowl2.f1(1);
}
void f2(int marker){
System.out.println("f2("+marker+")");
}
static Bowl bowl2= new Bowl(2);
}
class Cupboard{
Bowl bowl3=new Bowl(3);
static Bowl bowl4=new Bowl(4);
Cupboard(){
System.out.println("Cupboard()");
bowl4.f1(2);
}
void f3(int marker){
System.out.println("f3("+marker+")");
}
static Bowl bowl5= new Bowl(5);
}
public class StaticInitialization {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("Creating new Cupboard() in main");
new Cupboard();
System.out.println("Creating new Cupboard() in main");
new Cupboard();
table.f2(1);
cupboard.f3(1);
}
static Table table= new Table();
static Cupboard cupboard=new Cupboard();
}
/* Output:
Bowl(1)
Bowl(2)
Table()
f1(1)
Bowl(4)
Bowl(5)
Bowl(3)
Cupboard()
f1(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f1(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f1(2)
f2(1)
f3(1)
*///:~
可变参数列表
可变参数就是为了代替数组参数的,传多个参数后,在接收时自动变成了数组了。
static void test(int[] args){
System.out.println(Arrays.toString(args));
}
//调用
test(new int[] {1,2,3});
test(new int[] {1,2,3,4});
//输出
[1, 2, 3]
[1, 2, 3, 4]
static void test(int... args){
System.out.println(Arrays.toString(args));
}
//调用
test(1,2,3);
test(1,2,3,4);
//与上面输出结果一样
[1, 2, 3]
[1, 2, 3, 4]
内部类
如果需要在内部类里引用外部类的对象,需要使用外部类的名字.this
public class DotThis {
void f() { System.out.println("DotThis.f()"); }
public class Inner {
public DotThis outer() {
return DotThis.this;
// A plain "this" would be Inner's "this"
}
}
public Inner inner() { return new Inner(); }
public static void main(String[] args) {
DotThis dt = new DotThis(); //dt是外部类
Inner dti = dt.inner(); //dti是内部类
dti.outer().f(); //dti.outer()通过返回DotThis.this又返回了外部类
}
} /* Output:
DotThis.f()
*///:~
如果需要创建一个类的内部类对象,需要先创建外部类对象,然后使用 外部类对象.new 来创建内部类对象
public class Outer {
private Outer(){System.out.println("Outer Constructor");}
public class Inner{
Inner(){System.out.println("Inner Constructor");}
}
public static void main(String[] args) {
Outer outer = new Outer(); //创建外部类
Inner dni = outer.new Inner(); //创建内部类
//不可以像下面这样,会报错
//Outer.Inner inner = new Outer.Inner();
}
}
定义在方法中的内部类
下面的例子编译时报错,提示getInstace方法的返回类型错误,InnerImpl是方法里的内部类,它并不属于Demo类,在getInstace方法外根本找不到这个类。
public class Demo {
public static InnerImpl getInstace(){
class InnerImpl{
InnerImpl(){
System.out.println("Inner");
}
}
return new InnerImpl();
}
public static void main(String[] args) {
Demo.getInstace();
}
}
给InnerImpl添加一个接口类,然后返回这个接口类就可以了。
interface Inner{}
public class Demo {
public static Inner getInstace(){
class InnerImpl implements Inner{
InnerImpl(){
System.out.println("Inner");
}
}
return new InnerImpl();
}
public static void main(String[] args) {
Demo.getInstace();
}
}
匿名类
把上面示例修改一下,不再声明InnerImpl这个类名,而且使用new 接口的方式返回一个匿名类,
interface Inner{}
public class Demo {
public static Inner getInstace(){
return new Inner(){};
}
public static void main(String[] args) {
System.out.println(Demo.getInstace());
}
}
注意:匿名内部类没有构造方法,因为1.接口只是一个声明,需要有一个实现类实现这个接口。2.匿名内部类是匿名了,也就是说没有类名,所以就没有构造方法。
定义在作用域中的类
TrackingSlip类只能在if块中调用
public class Parcel6 {
private void internalTracking(boolean b) {
if(b) {
class TrackingSlip {
private String id;
TrackingSlip(String s) {
id = s;
}
String getSlip() { return id; }
}
TrackingSlip ts = new TrackingSlip("slip");
String s = ts.getSlip();
}
// 在作用域外使用TrackingSlip类会报错
//! TrackingSlip ts = new TrackingSlip("x");
}
public void track() { internalTracking(true); }
public static void main(String[] args) {
Parcel6 p = new Parcel6();
p.track();
}
} ///:~
内部类单独生成类文件
有名称的内部类(或接口)编译后生成一个名为 主类名$内部类名.calss 的类
匿名内部类编译后生成 一个名为 主类名$数字.calss 的类,数字从1开始计数
public class Stu{
interface A {
void onTouch();
}
public A setTouch() {
return new A() {
@Override
public void onTouch() {
}
};
}
public A setTouch2() {
return new A() {
@Override
public void onTouch() {
}
};
}
}
编译后生成:
Stu.class
Stu$A.class
Stu$1.class
Stu$2.class
内部类局部变量都必须是final
如果没加final,编译后系统会自动加上final修饰符
必须加final是为了保持参数值一致性,具体可看:
https://www.cnblogs.com/chenssy/p/3390871.html
https://blog.csdn.net/lazyer_dog/article/details/50669473
抽象类和接口的区别
https://blog.csdn.net/csdn_aiyang/article/details/71171886 解释非常形象
https://blog.csdn.net/dengminghli/article/details/71056874
抽象类是不能实例化的(不论这个抽象类里有没有构造函数)。
当抽象类的实现类实例化时,还是会隐式调用抽象类的无参构造方法的。
普通类A extends 抽象类B extends 抽象类C,则A需要重写b,c两个抽象内有所有抽象方法。
BaseAction可以是abstract类,因为它不需要new。
♦ 【一】抽象类与普通类
1、普通类可以去实例化调用;抽象类不能被实例化,因为它是存在于一种概念而不非具体。
2、普通类和抽象类都可以被继承,但是抽象类被继承后子类必须重写继承的抽象方法,除非子类也是抽象类。抽象类中的普通方法不需要子类重写。
(问答案例)
public class Pet {
public void play(){ //这是宠物类,普通父类,方法里是空的
}
}
--------------------------
public class Cat extends Pet { //这是子类,是一个猫类,重写了父类方法
public void play(){
System.out.println("1、猫爬树");
}
}
------------------------
public class Dog extends Pet { //这是子类,是一个狗类,重写了父类方法
public void play(){
System.out.println("2、狗啃骨头");
}
}
-------------------------
public class Test {
public static void main(String[] args) { //这是测试类,分别调用了子类的不同方法
Pet p1=new Dog(); //多典型的多态表现啊,相当的给力
Pet p2=new Cat();
p1.play();
p2.play();
}
}
-----------------------
输出结果:
2、狗啃骨头
1、猫爬树
-----------------------
问题:把父类改成抽象类,方法改成抽象方法,那么public void play();//抽象方法没方法体
子类不变,依然重写父类方法,那这个跟普通父类没区别啊?
难道说就一个抽象方法没方法体就完事了??那我普通方法有方法体,我空着不写内容不就得了,不跟抽象方法一个样吗??
别跟我说抽象类还不能实例化,哥也不需要去new它!
普通类都能搞定的,还弄个抽象类有什么意义?我前面都说了普通类的方法我可以空着不写,达到跟抽象类方法没方法体一样的效果。既然两种方式都能达到同一个输出效果,弄一种方式不就得了,那为什么还要创造出一个抽象类出来?难道是比普通类看着舒服?用着爽?还是更加便捷?还是为了强制让别人用的时候必须强制化实现抽象方法省的你忘了什么的?
答:就是为了强制不能实例化,以及强制子类必须实现方法这不是你忘不忘的问题,你说你不去new它就行了,这话没错
那你想另一个问题,为什么要有访问控制呢?为什么要有private和public之分呢?
我可以全部public,不该访问的,我不访问就行了啊?
小程序里,看不出什么区别,反而private成员要写一堆set和get函数,多麻烦,我自己写小程序的时候也会偷懒全部public
但是项目大了,代码多了,这种严谨的结构就很重要了。
且不说会有很多人合作一起写一个程序,哪怕还是你一个人写,也保不住有忘记的时候,那时候编译器不报错,茫茫码海上哪找错误去面向对象说到底就是方便你思考,模块化,易维护管理,硬要说没必要,整个面向对象都没必要了,C语言有什么干不了的呀,运行效率还高。
♦ 【二】抽象类与接口
1、概念不一样。接口是对动作的抽象,抽象类是对本质的抽象。
抽象类表示的是,这个对象是什么。接口表示的是,这个对象能做什么。比如,男人,女人,这两个类(如果是类的话……),他们的抽象类是人。说明,他们都是人。人可以吃东西,狗也可以吃东西,你可以把“吃东西”定义成一个接口,然后让这些类去实现它。所以,在高级语言上,一个类只能继承一个类(抽象类)(正如人不可能同时是生物和非生物),但是可以实现多个接口(吃饭接口、走路接口)。
2、使用情况:
a.抽象类 和 接口 都是用来抽象具体对象的. 但是接口的抽象级别最高
b.抽象类可以有具体的方法 和属性, 接口只能有抽象方法和不可变常量
c.抽象类主要用来抽象类别,接口主要用来抽象功能.
d.抽象类中,可以有普通方法也可以有抽象方法,抽象方法不能有方法体,普通方法可以有方法体,派生类必须覆盖抽象方法。接口中所有方法都必须是未实现的。
e.接口是设计的结果 ,抽象类是重构的结果
3、使用方向:当你关注一个事物的本质的时候,用抽象类;当你关注一个操作的时候,用接口。
【注意】
抽象类的功能要远超过接口,但是,定义抽象类的代价高。因为高级语言来说(从实际设计上来说也是)每个类只能继承一个类。在这个类中,你必须继承或编写出其所有子类的所有共性。虽然接口在功能上会弱化许多,但是它只是针对一个动作的描述。而且你可以在一个类中同时实现多个接口。在设计阶段会降低难度的。
♦ 【三】继承
一个类只能继承一个父类,但可以实现多个接口
接口可以继承接口,一个接口可以继承多个接口
interface a{
void a();
}
interface b extends a{
void b();
}
interface c extends b{
void c();
}
interface d extends c{
void d();
}
//e实现的d接口,e内部就要实现a,b,c,d接口内的所有抽象方法,因为a,b,c,d是依次继承的
class e implements d {
@Override
public void d(){}
@Override
public void c(){}
@Override
public void b(){}
@Override
public void a(){}
}
c接口可以继承a,b接口,d接口单独存在,这样e类只要implements d,c两个接口,这时也要把a,b接口内的抽象方法都要重写实现
interface a{
void a();
}
interface b{
void b();
}
interface c extends b,a{
void c();
}
interface d{
void d();
}
class e implements d,c {
@Override
public void d(){}
@Override
public void c(){}
@Override
public void b(){}
@Override
public void a(){}
}
泛型
public class Entity<A,B> {
private A a;
private B b;
public Entity (A a, B b) {
this.a = a;
this.b = b;
}
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
//因数Entity类名<>没有T,所以这里要有<T>
public <T> T getT(T a) {
return a;
}
//因为这是静态方法,不经过实例化就能调用,所以要有<A>
public static <A> A getA(A a) {
return a;
}
}
Entity entity = new Entity<>("a",1L);
Entity.getA(new Integer(2));
多态
所谓多态,就是指一个引用(类型)在不同的情况下的多种状态。Java中多态实现机制靠的是父类或接口定义的引用变量可以指向子类或其具体的实现类。两种形式可以实现多态。继承和接口。从一定角度来看,封装和继承几乎都是为多态而准备的
多态的作用:当把不同的子类对象都当做父类类型来看待,可以屏蔽不同子类对象之间的实现差异,从而写出通用的代码达到通用编程,以适应需求的不断变化。
Map map=new HashMap();也是多态。