JAVA基础巩固



  • JAVA基础巩固
    参考《effectiveJava》、《如何像计算机学家一样思考Java》、runoob.com、google等



  • 创建和销毁对象

    • 遇到多个构造器参数时要考虑用构建器:

      • javaBean模式,无参构造器+setter方法

      • Object.freeze()方法,被冻结的对象不能修改、添加、删除其属性或者属性值

      • Builder模式

        • public class NutritionFacts{
            private final int servingSize;
            private final int fat;
            
            public static class Builder{
              //Reqiired parameters
              private final int servingSize;
              //default
              private int fat = 0;
              
              public Builder(int servingSzie){
                this.servingSzie = servingSize;
              }
              
              public Builder fat(int fat){
                this.fat = fat;
              }
              
              public NutritionFacts build(){
                return new NutritionFacts(this);
              }
            }
            private NutritionFacts(Builder builder){
              servingSize = builder.servingSize;
              fat = builder.fat;
            }
          }
          
          //构造
          NutritionFacts cola = new NutritionFacts.Builder(240).fat(35).build();
          
    • 用私有构造器或者枚举类型强化Singleton属性(指仅仅被实例化一次的类)

      • //公有的静态成员
        //缺少公有的或者受保护的构造器,且私有构造器仅仅被调用一次,保证了Elvis的全局唯一性
        public class Elvis{
          public static final Elvis INSTANCE = new Elvis();
          private Elvis(){...}
          
          public void leaveTheBuilding(){...}
        }
        
      • //公有的成员是个静态工厂方法
        //调用getInstance都会返回同一个对象引用
        public class Elvis{
          private static final Elvis INSTANNCE = new Elvis();
          private Elvis(){...}
          public static Elvis getInstance{return INSTANCE;}
          
          public void leaveTheBuilding(){...}
        }
        
      • //Enum singleton 包含单个元素的枚举类型
        //与公有域方法大致相同,但更加简洁
        public enum Elvis{
          INSTANCE;
         
          public void leaveTheBuilding(){...}
        }
        
    • 通过私有构造器强化不可实例化能力

      • 工具化类不希望被实例化,类似于编写过程化语言

      • public class UtilityClass{
          //default constryctor for noninstantiability
          //构造器私有就能保证不能被实例化
          private UtilityClass(){
            throw new AssertionError();
          }
          
          ...
        }
        
    • 避免创建不必要的对象

      • //重复构造
        String s = new String("test");	
        //对同一台虚拟机中运行的代码,包含相同的字面常量,该对象就会被重用
        String s = "test";	
        
      • //重用不可变对象
        //eg.判断是否是生育高峰出生
        class person{
          private static final Data BIRTH_DATA;//出生日期
          private static final Data BOOM_START;//生育高峰
          private static final Data BOOM_END;//生育高峰结束
          
          //避免每一次调用判断方法都创建Calendar对象
          static{
            Calendar gmetCal = 
              Calendar.getInstance(TimdeZone.getTimeZone("GMT"));
            gmtCal.set(...);//设置开始时间
            BOOM_START = gmtCal.getTime();
            gmtCal.set(...);//结束时间
            BOOM_START = gmtCal.getTime();
          }
          
          public boolean isBabyBoomer(){
            return BIRTH_DATA.compareTo(BOOM_START) >=0&&
              BIRTH_DATA.compareTo(BOOM_END) <=0;
          }
        }
        
      • 对象池

    • 消除过期的对象引用

      • 对象数组中,element[index] = null,使对象被回收
      • 缓存导致的内存泄漏
      • 监听器和其他回调
    • 避免使用终结方法finalizer

      • finalize()方法是Object类中提供的一个方法,在GC准备释放对象所占用的内存空间之前,它将首先调用finalize()方法。

      • //调用时机不确定,导致性能的降低
        class User{
        	public static User user = null;
        
        	@Override
        	protected void finalize() throws Throwable {
        		System.out.println("User-->finalize()");
        		user = this;
        	}
        	
        }
        
        public class FinalizerTest {
        	public static void main(String[] args) throws InterruptedException {
        		User user = new User();
        		user = null;
        		System.gc();
        		Thread.sleep(1000);
        		
        		user = User.user;
        		System.out.println(user != null);//true
        		
        		user = null;
        		System.gc();
        		Thread.sleep(1000);
        		System.out.println(user != null);//false
        	}
        }
        


  • 类和接口(一)

    • 使类和成员的可访问性最小化

      • 信息隐藏(封装)是软件设计的基本原则之一,设计良好的模块会隐藏其所有实现模块

      • 第一规则:尽可能使每个类或成员不被外界访问

        • 可访问性递增:private、package-private、protected、public
      • //potential security hole
        //长度非零的数组总是可变的
        public static final Thing[] VALUES = {...};
        
        //第一种修正方法:
        private static final Thing[] PRIVATE_VALUES = {...};//将不可变数组变为私有
        public static final List<Thing> VALUES = 
          Collections.unmodefiableList(Arrays.asList(PRIVATE_VALUES));//复制增加一个公有不可变列表
        
        //第二种修正方法:
        //数组变成私有,增加一个公有方法,返回私有数组的拷贝
        private static final Thing[] PRIVATE_VALUES = {...};
        public static final Thing[] values{
          return PRIVATE_VALUES.clone();
        }
        
      • 总而言之,就是降低内部的可访问性

    • 在公有类中使用访问方法而非公有域

      • 对于可变类来说,应该用包含私有域和公有getter和setter方法来代替
    • 使可变性最小化

      • 不可变类是实例不能被修改的类,每个实例所包含的信息都必须在创建该实例的时候就提供,在lifetie内不变,包括String、BigInteger、BigDecimal
      • 不可变类的规则:
        • 不要提供mutator
        • 保证类不会被扩展,防止子类化一般用final直接修饰
      • 使所有域都是final的
      • 使所有的域都是私有的
      • 确保对于任何可变组件的互斥访问,尽量使用保护性拷贝方案
    • //eg.复数类
      
      public final class Complex{
        private final double re;
        private final double im;
        
        public Complex(double re,double im){
          this.re = re;
          this.im = im;
        }
        
        //no corresponding mutators
        public double realPart(){return re;}
        public double imaginaryPart(){return im;}
        
        //计算方法
        public Complex add(Complex c){
          return new Complex(re + c.re,im+c.im);
        }
        public Complex subtract(...){...}
        public Complex divide(...){...}
        public Complex multiply(...){...}
        
        //override
        @Override
        public boolean equals(Object o){
          if(o == this) return true;
          if(!(o instanceof Complex)) return false;
          Complex c = (Complex) o;
          
          return Double.compare(re.c.re) == 0 &&
            Double.compare(imm,c.im) == 0;
        }
        @override
        public in hashCode(){
          int result = 17 + hashDouble(re);
          result = 31*result + hashDouble(im);
          return result;
        }
        @override public String toStrong(){
          return "(" + re + "+" + im + "i)"
        }
      }
      
      • 在算数运算中返回新的Complex实例,而不是修改这个实例,称为functional做法

      • 不可变对象可以被自由的共享

      • 不可变对象真正唯一的缺点是:对于每个不同的值都需要一个单独的对象

      • 不可变类除了final修饰之外,还有让类的所有构造器都变成私有的或者包级私有的,并添加公有的静态工厂

        public class Complex{
          private final double re;
          private final double im;
          
          private Complex(double re,double im){
            this.re = re;
            this.im = im;
          }
          
          public static Complex valueOf(double re,double im){
            return new Complex(re,im);
          }
          
          ...//Remainder unchanged
        }
        
        //例如基于极坐标创建复数的静态方法
        //只需添加静态工厂,其名称可以直接的反应其功能
        public static Complex valueOfPolar(double r,double theta){
          return new Complex(r * Math.cos(theta),r * Math.sin(theta));
        }
        
    • 复合优先于继承

      • 一般好的方法是覆盖时直接重写该方法
        • 复合:不用扩展现有的类,直接在新的类中增加一个私有域,引用现有的类的实例
        • 转发:新类中的每个实例方法都可以调用被包含的现有的类实例中对应的方法,并返回他的结果,这被称为转发
    • 要么为继承而设计,并提供文档说明,要么就禁止继承

      • 构造器绝不能调用可被override的方法

        //构造器调用了可被覆盖的方法
        public class Super{
          public Super(){
            overrideMe();
          }
          
          public void overrideMe(){
            ...
          }
        }
        
      • 下面的子类覆盖了方法overrideMe,Super唯一的构造器就调用了这个方法

        public final class Sub extends Super{
          private final Dta data;
          
          Sub(){
            data = new Data();
          }
          
          @Override
          public void overrideMe(){
            ...
          }
          
          public static void main(String[] args){
            Sub sub = new Sub();
            sub.overrideMe();
          }
        }
        
        /*
        第一次打印出null,override被Super构造器调用的时候,构造器Sub还没有机会初始化data域
        第二次打印出时间
        */
        
      • 如果必须继承,合理的方法就是确保这个类不会调用它的任何可覆盖的方法(完全消除这个类中可覆盖方法的自用特性)



  • 类和接口(二)

    • 接口优于抽象类

      • 现有的类可以很容易被更新,以实现新的接口

      • 接口是定义mixin的理想选择

      • 接口允许我们构造非层次结构的类型框架

        • //歌手和作曲家
          public interface Singer{...}
          public interface SongWriter{...}
          
          //可能既是歌手又是作曲家
          public interface SingerSongWriter extends Singer,SongWriter{...}
          
        • 若不这么做,可以对每一种都编写一个单独的类,如果整个系统有n个属性,那么有2^n钟可能的组合,被称为“组合爆炸”

      • 对你导出的每一个重要接口都提供一个抽象的骨架实现类(skeletal implementation),把接口和抽象类的优点结合起来,接口不允许方法的实现,但是用接口定义类型不妨碍为程序员的实现提供帮助

        • 骨架实现

          static List<Integer> intArrayAsList(final int[] a){
            if(a==null)
              throw new NullPointerException();
            
            return new AbstractiList<Lnteger>(){
              
              public Integer get(int i){
                return a[i];
              }
              
              @override
              public Integer set(int i,Integer val){
                int oldVal = a[i];
                a[i] = val;
                return oldVal;
              }
              
              public int size(){
                return a.length;
              }
            };
          }
          
    • 接口只用于定义类型

      • 常量接口(constant interface),这种接口没有包含任何方法,只包含静态final域,每个域都导出一个常量,使用这些常量的类实现这个接口,以避免用类名来修饰常量

        public interface PhysicalConstants{
          static final double AVOGADROS_NUMBER = 6.022;
          static final double BOLTZMANN_CONSTANT = 1.380;
          static final double ELECTRON_MASS = 9.109;
        }
        
        • 常量接口模式是对接口的不良使用,如果要导出常量

          public class PhysicalConstants{
            private PhysicalConstants(){}
            public static final AVOGADROS_NUMBER = 6.022;
            public static final double BOLTZMANN_CONSTANT = 1.380;
            public static final double ELECTRON_MASS = 9.109;
          }
          
          //使用
          PhysicalContants.AVOGADROS_NUMBER
          
          //静态导入机制
          import static ....PhysicalConstants.*;
          
          public class Test{
            double atoms(double mols){
              //不用加类名
              return AVOGADROS_NUMBER * mols;
            }
            ...
          }
          
    • 类层次优于标签类

      • 为标签类的每个方法定义一个抽象类,每个方法的行为都依附于标签值

        //抽象类
        abstract class Figure{
          abstract double area();
        }
        class Circle entends Figure{
          final double redius;
          
          Circle(double redius){this.radius = redius;}
          
          double area(){return Math.PI*(radius * radius);}
        }
        class Rectangle entends Figure{
          final double length;
          final double width;
          
          Rectangle(double length,double width){
            this.length = length;
            this.width = width;
          }
          
          double area(){return length*width;}
        }
        
        //use
        class Square entends Rectangle{
          Square(double side){
            super(side,side);
          }
        }
        
    • 用函数对象表示策略

    • 优先考虑静态成员类

      • 嵌套类:被定义在另一个类的内部的类

        • 嵌套类的存在是为它的外围类提供服务
        • 如果嵌套类将来可能用于其他某个环境中,它就是顶层类
        • 嵌套类有四种:静态成员类、非静态成员类、匿名类、局部类(除第一种之外,其他的三种都被称为内部类)
      • 静态成员类是最简单的一种嵌套类,可以被看作普通类被声明在另一个类的内部而已;静态成员类的一种常见方法是作为公有类的辅助类,仅当与它的外部类一起使用时才有意义

      • 如果嵌套类的实例可以在它的外围类的实例之外独立存在,这个嵌套类就必须是静态成员类;在没有外围实例的情况下,要想创建非静态成员类的实例是不可能的

      • 非静态成员类的一种常见用法是定义一个Adapter,它允许外部类的实例被看作是另一个不相关的类的实例

        public class Myset<E> extends AbstractSet<E>{
          
          public Iterator<E> iterator(){
            return new MyIterator();
          }
          
          //非静态成员类的使用
          private class MyIterator implements Iterator<E> {
            ...
          }
        }
        
        • 如果声明成员类不要求访问外围实例,就始终把static修饰符放在它的声明中,在静态和非静态之间做出选择是非常重要的
      • 匿名类

        • 常见用法:动态创建函数对象、创建过程对象processObject、在静态工厂方法的内部
        • 除了在它们被声明的时候之外,是无法将他们实例化的,不能执行instanceof测试
        • 做任何需要命名类的其他事情,无法声明一个匿名类来实现多个接口,或者扩展一个类并同时扩展类和实现接口
      • 局部类

        • 在任何可以声明局部变量的地方可以声明局部类,并且局部类也遵守同样的作用域规则

          • 与成员类一样,局部类有名字,可以被重复使用

          • 与匿名类一样,只有当局部类是在非静态环境中定义的时候,才有外围实例,它们也不能包含静态成员



  • 泛型Generic

    • 不要在新代码中使用原生态类型

      • 声明中具有一个或多个typeParameter的类或者接口,就是泛型类或者接口。例如List<E>

      • 每种泛型定义一组参数化的类型,格式为:先是类或者接口的名称,接着用<>把对应于泛型形式类型的时机类型参数列表扩起来,例如List<String>其中String是与形式类型参数E相对应的实际类型参数

      • 每个泛型都定义一个原生态类型rawType,即不带任何实际类型参数的泛型名称,例如List<E>

      • //java1.5之前
        private final Collection stamps = ...;
        stamps.add(new Coin(...));//仍然不报错
        //直到从stamp集合获取coin时才会收到错误提示
        for(Iterator i = stamps.iterator();i.hasNext();){
          Stamp s =(Stamp) i.next();//这里Throws ClassCastException
        }
        
      • 有了泛型之后,就可以直接声明

        //直接声明,告诉编译器放的Stamp
        private final Collection<Stamp> stamps = ...;
        
      • 使用List这样的原生态类型,会失掉类型安全性,但是如果使用像List<Object>则不会

        //fails at runtime
        public static void main(String[] args){
          List<String> strings = new ArrayList<String>();
          unsafeAdd(strings,new integer(42));
          String s = strings.get(0);
        }
        
        private static void unsafeAdd(List list,Object o){
          list.add(o)//warning
        }
        
        //可用这种方式编写
        //但他使用了原生态类型
        static int numElementsInCommon(Set s1,Set s2){
          int result = 0;
          for(Object 01:s1)
            if(s2.contains(01))
              result++;
          return result;
        }
        
        • 无限制的通配符类型,如果要使用泛型但不确定或者不关心实际的类型参数,就可以使用一个问号代替
        //Set<E>的无限制通配符类型为Set<?>
        
        //这样做是安全的
        static int numElementsInCommon(Set<?> s1,Set<?> s2){
          int result = 0;
          for(Object o1:s1)
            if(s2.contains(o1))
              result++;
          return result;
        }
        
    • 消除非受检警告



  • 枚举和注解

    • enum(枚举)和annotationType(注解类型)

    • 用enum代替int常量

      • enum type是指由一组固定的常量组合成合法值的类型,例如一年中的季节,一副牌的花色;

        //在还没有引入枚举类型之前,常用模式是声明一组具名的int常量
        //称为int枚举模式
        public static final int APPLE_FUJI = 0;
        public static final int APPLE_PIPPIN = 1;
        public static final int APPLE_GRANNY_SMITH = 2;
        
        //类型安全的枚举
        public enum Apple {FUJI,PIPPIN,GRANNY_SMITH}
        public enum Orange {NAVEL,TEMPLE,BLOOD}
        
      • public enum Planet{
          //行星
          MERCURY(...,...);
          VENUS(...,...);
          EARTH(...,...);
          
          private final double mass;
          private final double radius;
          private final double surfaceGravity;
          
          private static final double G = 6.63;
          
          Planet(double mass,double radius){
            this.mass = mass;
            this.radius = radius;
            surfaceGravity = G*mass/(radius*radius);
          }
          
          public double mass(){return mass;}
          public double radius(){return radius;}
          public double surfaceGravity(){return surfaceGravity;}
        
          public double surfaceWeight(double mass){
            return mass*surfaceGravity;
          }
        }
        
      • //运算
        public enum Operation{
          PLUS,MINUS,TIMES,DIVIDE;
          
          double apply(double x,double y){
            switch(this){
              case PLUS: 		return x+y;
              case MINUS: 	return x-y;
              case Times: 	return x*y;
              case DIVIDE: 	return x/y;
            }
            throw new AssertionError("Unknow op:"+this);
          }
        }
        
        //运算
        public enum Operation{
          PLUS		{double apply(double x,double y){return x+y;}},
          MINUS		{double apply(double x,double y){return x-y;}},
          TIMES		{double apply(double x,double y){return x*y;}},
          DIVIDE	{double apply(double x,double y){return x/y;}};
          
          abstract double apply(double x,double y);
        }
        
      • //特定于常量的方法实现可以与特定于常量的数据结合起来
        public enum Operation{
          PLUS("+"){
            double apply(double x,double y){return x+y;}
          },
          MINUS("-"){
            double apply(double x,double y){return x-y;}
          },
          TIMES("*"){
            double apply(double x,double y){return x*y;}
          },
          DIVIDE("/")	{
            double apply(double x,double y){return x/y;}
          };
          
          private final String symbol;
          Operation(String symbol){this.symbol = symbol;}
          @override public String toString(){return symbol;}//覆盖toString方法
          
          abstract double apply(double x,double y);
        }
        
    • 用实例域代替序数

      • 枚举都天生和一个int值相关,所有枚举都有一个ordinal方法,它返回每个枚举在类型中的数字位置,从序数中得到关联的int值

        public enum Ensemble{
          SOLO,		DUET,		TRIO,	QUARTET,	QUINTET,
          SEXTET,	SEPTET,	OCTET,NONET,		DECTET;
          
          public int numberOfMusicians(){return ordinal()+1;}
        }
        
    • 用EnumSet代替位域

      • 如果一个枚举类型的元素主要用在集合中,一般就使用int枚举模式

      • Java.util包提供了EnumSet类来有效地表示从单个枚举类型中提取的多个值的多个集合。

      • public class Text{
          public enum Style{BOLD,ITALIC,UNDERLINE,STRIKETHROUGH}
          
          public void applyStyle(Set<Style> style){...}
        }
        
        //使用
        text.applyStyle(EnumSet.of(Style.BOLD,Style.TTALIC));
        
    • 用EnumMap代替序数索引

      • ordinal方法:获取索引

      • //例子:表示一种烹饪用的香草
        public class Herb{
          public enum Type{ANNUAL,PERENNIAL,BIENNIAL}
          
          private final String name;
          private final Type type;
          
          Herb(String name,Type type){
            this.name = name;
            this.type = type;
          }
          
          @Override public String toString(){
            return name;
          }
        }
        
        //假设有一个香草数组,表示一座花园中的植物,下面要按照类型进行组织后将植物列出来
        //1.可以构建三个集合,并遍历整个花园,分别放入相应的集合
        //2.将这些集合放到一个按照类型的叙述进行索引的数组中来实现
        Herb[] garden = ...;
        
        Set<Herb>[] herbsByType = (Set<Herb>[]) new Set[Herb.Type.values().length];
        for(int i = 0;i<herbsByType.length;i++) herbsByType[h.type.ordinal)()].add(h);
        
        for(Herb h:garden) herbsByType[h.type.ordinal()].add(h);
        
        //Print the results
        for(int i = 0;i <herbsBytype.length;i++){
          System.out.printf("%s:%s%n",Herb.Type.values()[i],herbsByType[i]);
        }
        
      • 有一种更好的方法可以达到同样的效果,数组实际上充当着从枚举到值的映射,因此可能还要用到Map。称作EnumMap

        Map<Herb.Type,Set<Herb>> herbsByType = new EnumMap<Herb.Type,Set<Herb>>(Herb.Type.class);
        for(Herb.Type t : Herb.Type.values())
          herbsByType.put(t,new HashSet<Herb>());
        for(Herb h:garden)
          herbsByType.get(h.type).add(h);
        System.out.println(herbsByType);
        
    • 用接口模拟可伸缩的枚举

      • public interface Operation{
          double apply(double x,double y);
        }
        
        public enum BasicOperation,implements Operation{
          PLUS("+"){
            public double apply(double x,double y){return x+y;}
          },
          MINUS("-"){
            public double apply(double x,double y){return x -y ;}
          };
          
          private final String symbol;
          BasicOperation(String symbol){
            this.symbol = symbol;
          }
          @Override public String toString(){
            return symbol;
          }
        }
        
        //使用
        public static <T extends Enum<T> & Operation> void test(Class<T> opSet,double x,double y){
          for(Operation op:opSet.getEnumConstants()){
            System.out.printf(op.apply(x,y));
          }
        }
        
        • opSet参数中公认很复杂的声明(<T extends Enum<T> & Operation> Class<T>)确保了Class对象既表示枚举又表示Operation的子类型,还有一种方法是使用Collection<? Extends Operation>
        public static void test(Collection<? extends Operation> opSet,double x,double y){
          for(Operation op:opSet){
            ...
          }
        }
        
    • 注解优先于命名模式

      Java1.5发行前,一般使用命名模式表明有些程序需要通过某些工具或者框架进行特殊处理

      • 命名模式有几个很严重的缺点:

        • 命名错误不会报错
        • 无法确定他们只用于相应的程序元素上
        • 没有提供将参数值与程序元素关联起来的好方法
      • 注解很好的解决了这个问题:

        import java.lang.annotation.*;
        
        @Retention(RetentionPolicy.RUNTIME)
        @Target(ElementType.METHOD)
        public @interface Test{
        }
        
        • Test注解类型的声明就是它自身通过Retention和Target注解进行了注解。注解类型声明中的这种注解被称作元注解(meta-annotation).@Retention(RetentionPolicy.RUNTIME)元注解表明,Test注解应该在运行时保留,如果没有保留,测试工具就无法知道test注解@Target(Element.METHOD)元注解表明,Test注解只在方法声明中才是合法的:他不能运用到类声明、域声明或者其他程序元素上。

        • 应用Test注解,称作标记注解(marker annotation),因为它没有参数,只是标注被注解的元素。如果程序员拼错了Test,或者将Test注解应用到程序元素而非方法声明,程序就无法编译。

          public class Sample{
            @Test public static void m1(){} //Test should pass
            public static void m2(){}				
            @Test public static void m3(){	//test should fail
              throw new RuntimeException("Boom");
            }
            public static void m4(){}
            @Test public void m5(){}				//INVALID USE:nonstatic method
            public static void m6(){}
            @Test public static void m7(){	//Test should fail
              throw new RuntimeException("Crash");
            }
            public static void m8(){ }
          }
          
          //Sample包含四项测试:一项会通过,两项会失败,另一项无效,没有标注的4个方法会被测试工具忽略
          
      • 注解永远不会改变被注解代码的语义,但是使他可以通过工具进行特殊的处理

        //测试注解
        public class RunTests{
          public static void main(String[] args) throws Exception{
            int tests = 0;
            int passed = 0;
            Class testClass = Class.forName(args[0]);
            //运行类中的所有方法
            for(Method m : testClass.getDeclaredMethods()){
              if(m.isAnnotationPresent(Test.class)){
                tests++;
                try{
                  m.invoke(null);
                  passed++;
                }catch(InvocationTargetException wrappedExc){
                  Throwable exc = wrappedExc.getCause();
                  System.out.println(m+"failed"+exc);
                }catch(Exception exc){
                  System.out.println("INVALID @Test:"+m);
                }
              }
            }
            System.out.printf("Passed:%d,Failed:%d%n",passed,tests-passed);
          }
        }
        
        //output
        public sta
        Passed:1,Failed:3
        
    • 坚持使用Override注解

      • @override注解只能用在方法声明中。表示被注解的方法声明覆盖了超类中的一个声明,如果坚持使用这个注解,可以防止一大类非法错误

        public class Bigram{
          private final char first;
          private final char second;
          public Bigram(char first,char second){
            this.first = first;
            this.second = second;
          }
          public boolean equals(Bigram b){
            return b.first == first && b.second == second;
          }
          public int hashCode(){
            return 31*first + second;
          }
          public static void main(String[] args){
            Set<Bigram> s = new HashSet<Bigram>();
            for(int i = 0;i<10;i++){
              for(char ch = 'a';ch <= 'z';ch++){
                s.add(new Bigram(ch,ch));
              }
            }
            System.out.println(s.size());
          }
        }
        

        equals没有被覆盖,而是被重载,,为了覆盖Object.equals,必须定义一个参数为Object类型的equals方法,在这个例子中,Bigram是继承了equals方法

        @Override
        public boolean equals(Object o){
          if(!(o instanceof Bigram))
            return false;
          Bigram b = (Bigram)o;
          return b.first == first && b.second == second;
        }
        

        应该要在你想要覆盖超类声明的每个方法声明中使用Override注解

    • 用标记接口定义类型(markerInterfacfe)

      • 标记接口是没有包含方法声明的接口,而只是指明一个类实现了具有某种属性的接口
      • 标记接口定义的类型是由被标记类的实例实现的;标记注解则没有定义这样的类型。
      • 标记接口胜过标记注解的另一个优点是,它们可以被更加精确地锁定。如果注解类型利用@Target(ElementType.TYPE)声明,它就可以被应用到任何类或者接口。假设有一个标记只适用于特殊接口的实现。如果将它定义成一个标记接口,就可以用它将唯一的接口扩展成它适用的接口。
      • 标记注解胜过标记接口最大的优点在于,他可以通过默认的方式添加一个或多个注解类型元素,给已被使用的注解类型添加更多的信息,随着时间的推移,简单的标记注解类型可以演变成更加丰富的注解类型
      • 什么时候使用标记注解,什么时候应该使用标记接口呢?
        • 如果标记是应用到任何程序元素而不是类或者接口,就必须使用注解,因为只有类和接口可以用来实现或者拓展接口
        • 如果标记只应用给类和接口,就要思考编写一个还是多个只接受有这种标记的方法,如果是这种情况,就应该优先使用标记接口而非注解,这样就可以用接口作为相关方法的参数类型


  • 请问如何像计算机学家一样思考java呢



  • @throwingup 首先你要有一个对象,然后就拥有了OOA、OOD、OOP的能力。



  • @foresight 谢谢大神!



  • 可以分享一下 <? extends T><? super T> 相关的用法吗?



  • 泛型--<? extends T> 和 <? super T>

    基本介绍

    • <? extends T> 是指 “上界通配符(Upper Bounds Wildcards)

      • 表示类型的上界,表示参数化类型的可能是T或是T的子类;

      • //例子
        //表示一个能放水果以及一切是水果派生类的盘子
        Plate<? extends Fruit>
        
    • <? super T> 是指 “下界通配符(Lower Bounds Wildcards)

      • 表示类型下界(Java Core中叫超类型限定),表示参数化类型是此类型的超类型(父类型),直至Object;

      • //例子
        //一个能放水果以及一切是水果基类的盘子
        Plate<? super Fruit>
        

    使用

    • 上界<? extends T>不能往里存,只能往外取

      • set()方法失效,get()方法还有效

        Plate<? extends Fruit> fruitPlate = new Plate<Apple>(new Apple());
        
        //不能存入任何元素
        fruitPlate.set(new Apple());    //Error  编译错误
        fruitPlate.set(new Fruit());    //Error  编译错误
        
        //读取出来的东西只能放在Fruit或它的基类里
        Fruit newFruit1=fruitPlate.get();
        Object newFruit2 = fruitPlate.get();
        Apple newFruit3 = fruitPlate.get();  //Error
        
    • 下界<? super T>不影响往里存,但往外取只能放在Object对象里

      • get( )方法部分失效,只能存放到Object对象里;set( )方法正常。

        Plate<? super Fruit> fruitPlate = new Plate<Fruit>(new Fruit());
        
        //存入元素正常
        fruitPlate.set(new Apple());
        fruitPlate.set(new Fruit());
        
        //读取出来的东西只能存放在Object类里
        Object newFruit1 = fruitPlate.get();
        Fruit newFruit2 = fruitPlate.get();  //Error
        Apple newFruit3 = fruitPlate.get();  //Error
        

    PECS原则

    • PECS(Producer Extends Consumer Super)原则:
      • 频繁往外读取内容的,适合用上界Extends。
      • 经常往里插入的,适合用下界Super。

 

Copyright © 2018 bbs.dian.org.cn All rights reserved.

与 Dian 的连接断开,我们正在尝试重连,请耐心等待